diff --git a/res/css/views/avatars/_BaseAvatar.pcss b/res/css/views/avatars/_BaseAvatar.pcss index 52fd8452d3..dd643ebfb5 100644 --- a/res/css/views/avatars/_BaseAvatar.pcss +++ b/res/css/views/avatars/_BaseAvatar.pcss @@ -22,3 +22,10 @@ limitations under the License. color: white !important; } } + +button.mx_BaseAvatar { + /* The user agent stylesheet overrides the font-size in this scenario + And that breaks the alignment, emojis, and all sorts of things + */ + font-size: inherit; +} diff --git a/res/css/views/rooms/_BasicMessageComposer.pcss b/res/css/views/rooms/_BasicMessageComposer.pcss index 7b88a05815..e09eaa5a04 100644 --- a/res/css/views/rooms/_BasicMessageComposer.pcss +++ b/res/css/views/rooms/_BasicMessageComposer.pcss @@ -79,7 +79,7 @@ limitations under the License. height: $font-16px; margin-inline-end: 0.24rem; background: var(--avatar-background), $background; - color: $avatar-initial-color; + color: var(--avatar-color, $avatar-initial-color); background-repeat: no-repeat; background-size: $font-16px; border-radius: $font-16px; diff --git a/res/css/views/rooms/_ReadReceiptGroup.pcss b/res/css/views/rooms/_ReadReceiptGroup.pcss index 16efa8a513..b09b818a7a 100644 --- a/res/css/views/rooms/_ReadReceiptGroup.pcss +++ b/res/css/views/rooms/_ReadReceiptGroup.pcss @@ -49,13 +49,11 @@ limitations under the License. height: 100%; .mx_BaseAvatar { + box-sizing: content-box; position: absolute; - display: inline-block; - height: 14px; - width: 14px; border: 1px solid $background; - border-radius: 100%; - + width: 14px; + height: 14px; will-change: left, top; transition: left var(--transition-short) ease-out, top var(--transition-standard) ease-out; } diff --git a/res/css/views/rooms/_ThreadSummary.pcss b/res/css/views/rooms/_ThreadSummary.pcss index 34fe50e989..78a9d9e529 100644 --- a/res/css/views/rooms/_ThreadSummary.pcss +++ b/res/css/views/rooms/_ThreadSummary.pcss @@ -123,6 +123,7 @@ limitations under the License. .mx_ThreadSummary_avatar { margin-inline-end: $spacing-8; + flex-shrink: 0; } .mx_ThreadSummary_icon { diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index 50f7663598..c4591c4c07 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -148,8 +148,8 @@ limitations under the License. background-size: $font-16px; border-radius: $font-16px; - color: $avatar-initial-color; - font-weight: normal; + color: var(--avatar-color, $avatar-initial-color); + font-weight: bold; font-size: $font-10-4px; } } diff --git a/src/Avatar.ts b/src/Avatar.ts index 13dd280066..7e54c634c5 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -15,12 +15,21 @@ limitations under the License. */ import { RoomMember, User, Room, ResizeMethod } from "matrix-js-sdk/src/matrix"; +import { useIdColorHash } from "@vector-im/compound-web"; import DMRoomMap from "./utils/DMRoomMap"; import { mediaFromMxc } from "./customisations/Media"; import { isLocalRoom } from "./utils/localRoom/isLocalRoom"; import { getFirstGrapheme } from "./utils/strings"; +/** + * 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"]; + // Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember( member: RoomMember | undefined, @@ -41,6 +50,18 @@ export function avatarUrlForMember( return url; } +/** + * Determines the HEX color to use in the avatar pills + * @param id the user or room ID + * @returns the text color to use on the avatar + */ +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]; +} + export function avatarUrlForUser( user: Pick, width: number, @@ -85,16 +106,12 @@ const colorToDataURLCache = new Map(); export function defaultAvatarUrlForString(s: string): string { if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake - const defaultColors = ["#0DBD8B", "#368bd6", "#ac3ba8"]; - let total = 0; - for (let i = 0; i < s.length; ++i) { - total += s.charCodeAt(i); - } - const colorIndex = total % defaultColors.length; + // eslint-disable-next-line react-hooks/rules-of-hooks + const colorIndex = useIdColorHash(s); // overwritten color value in custom themes const cssVariable = `--avatar-background-colors_${colorIndex}`; const cssValue = getComputedStyle(document.body).getPropertyValue(cssVariable); - const color = cssValue || defaultColors[colorIndex]; + const color = cssValue || AVATAR_BG_COLORS[colorIndex - 1]; let dataUrl = colorToDataURLCache.get(color); if (!dataUrl) { // validate color as this can come from account_data diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index d01a5affe0..40932cf724 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -99,7 +99,7 @@ const BaseAvatar: React.FC = (props) => { const { name, idName, - title, + title = "", url, urls, size = "40px", diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index c89e9d706b..c7a8670c6b 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -84,6 +84,25 @@ export function getMentionDisplayText(completion: ICompletion, client: MatrixCli return ""; } +function getCSSProperties({ + url, + initialLetter, + id = "", +}: { + url: string; + initialLetter?: string; + id: string; +}): string { + const cssProperties = [`--avatar-background: url(${url})`, `--avatar-letter: '${initialLetter}'`]; + + const textColor = Avatar.getAvatarTextColor(id); + if (textColor) { + cssProperties.push(textColor); + } + + return cssProperties.join("; "); +} + /** * For a given completion, the attributes will change depending on the completion type * @@ -118,7 +137,14 @@ export function getMentionAttributes( } attributes.set("data-mention-type", completion.type); - attributes.set("style", `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`); + attributes.set( + "style", + getCSSProperties({ + url: avatarUrl, + initialLetter, + id: mentionedMember.userId, + }), + ); } else if (completion.type === "room") { // logic as used in RoomPillPart.setAvatar in parts.ts const mentionedRoom = getRoomFromCompletion(completion, client); @@ -132,7 +158,14 @@ export function getMentionAttributes( } attributes.set("data-mention-type", completion.type); - attributes.set("style", `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`); + attributes.set( + "style", + getCSSProperties({ + url: avatarUrl, + initialLetter, + id: mentionedRoom?.roomId ?? aliasFromCompletion, + }), + ); } else if (completion.type === "at-room") { // logic as used in RoomPillPart.setAvatar in parts.ts, but now we know the current room // from the arguments passed @@ -145,7 +178,14 @@ export function getMentionAttributes( } attributes.set("data-mention-type", completion.type); - attributes.set("style", `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`); + attributes.set( + "style", + getCSSProperties({ + url: avatarUrl, + initialLetter, + id: room.roomId, + }), + ); } return attributes; diff --git a/test/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/components/structures/__snapshots__/RoomView-test.tsx.snap index 19df74f76d..6d678141dc 100644 --- a/test/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -24,6 +24,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1 data-type="round" role="presentation" style="--cpd-avatar-size: 24px;" + title="" > u @@ -106,6 +107,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`] data-type="round" role="presentation" style="--cpd-avatar-size: 24px;" + title="" > u @@ -185,6 +187,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`] data-type="round" role="button" style="--cpd-avatar-size: 52px;" + title="" > u @@ -273,6 +276,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] = data-type="round" role="presentation" style="--cpd-avatar-size: 24px;" + title="" > u @@ -352,6 +356,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] = data-type="round" role="button" style="--cpd-avatar-size: 52px;" + title="" > u @@ -515,6 +520,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t data-type="round" role="presentation" style="--cpd-avatar-size: 24px;" + title="" > u @@ -593,6 +599,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t data-type="round" role="button" style="--cpd-avatar-size: 52px;" + title="" > u diff --git a/test/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap b/test/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap index c99d5ee07d..1e7edb176c 100644 --- a/test/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap +++ b/test/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap @@ -77,6 +77,7 @@ exports[`SpaceHierarchy renders 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 20px;" + title="" > U @@ -147,6 +148,7 @@ exports[`SpaceHierarchy renders 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 20px;" + title="" > U @@ -218,6 +220,7 @@ exports[`SpaceHierarchy renders 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 20px;" + title="" > N @@ -295,6 +298,7 @@ exports[`SpaceHierarchy renders 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 20px;" + title="" > N diff --git a/test/components/structures/__snapshots__/UserMenu-test.tsx.snap b/test/components/structures/__snapshots__/UserMenu-test.tsx.snap index 54512dd379..d8003602f4 100644 --- a/test/components/structures/__snapshots__/UserMenu-test.tsx.snap +++ b/test/components/structures/__snapshots__/UserMenu-test.tsx.snap @@ -24,6 +24,7 @@ exports[` when rendered should render as expected 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > u diff --git a/test/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap b/test/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap index 20498fd510..cd545107be 100644 --- a/test/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap +++ b/test/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap @@ -9,6 +9,7 @@ exports[`RoomAvatar should render as expected for a DM room 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > D @@ -24,6 +25,7 @@ exports[`RoomAvatar should render as expected for a LocalRoom 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > l @@ -39,6 +41,7 @@ exports[`RoomAvatar should render as expected for a Room 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > t diff --git a/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap b/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap index 3a4810e2e2..11178c8cbd 100644 --- a/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap @@ -38,6 +38,7 @@ exports[` renders sidebar correctly with beacons 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > diff --git a/test/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap index b26369b308..0e29403ed5 100644 --- a/test/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap @@ -77,6 +77,7 @@ exports[` should list spaces which are not par data-type="round" role="presentation" style="--cpd-avatar-size: 20px;" + title="" > O diff --git a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap index 0693f40f66..682bb61314 100644 --- a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap +++ b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap @@ -109,6 +109,7 @@ exports[`AppTile for a pinned widget should render 1`] = ` data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 20px;" + title="" > u @@ -352,6 +355,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = ` data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 20px;" + title="" > renders with a tooltip 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > 4 diff --git a/test/components/views/elements/__snapshots__/Pill-test.tsx.snap b/test/components/views/elements/__snapshots__/Pill-test.tsx.snap index 1015798287..a7bedd5f48 100644 --- a/test/components/views/elements/__snapshots__/Pill-test.tsx.snap +++ b/test/components/views/elements/__snapshots__/Pill-test.tsx.snap @@ -41,6 +41,7 @@ exports[` should render the expected pill for @room 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > R @@ -72,6 +73,7 @@ exports[` should render the expected pill for a known user not in the room data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > U @@ -103,6 +105,7 @@ exports[` should render the expected pill for a message in another room 1` data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > R @@ -134,6 +137,7 @@ exports[` should render the expected pill for a message in the same room 1 data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > U @@ -165,6 +169,7 @@ exports[` should render the expected pill for a room alias 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > R @@ -196,6 +201,7 @@ exports[` should render the expected pill for a space 1`] = ` data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > S @@ -250,6 +256,7 @@ exports[` when rendering a pill for a room should render the expected pill data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > R @@ -281,6 +288,7 @@ exports[` when rendering a pill for a user in the room should render as ex data-type="round" role="presentation" style="--cpd-avatar-size: 16px;" + title="" > U diff --git a/test/components/views/messages/TextualBody-test.tsx b/test/components/views/messages/TextualBody-test.tsx index 5a51e6a2b7..a6b22a7412 100644 --- a/test/components/views/messages/TextualBody-test.tsx +++ b/test/components/views/messages/TextualBody-test.tsx @@ -199,7 +199,7 @@ describe("", () => { const { container } = getComponent({ mxEvent: ev }); const content = container.querySelector(".mx_EventTile_body"); expect(content.innerHTML).toMatchInlineSnapshot( - `"Chat with Member"`, + `"Chat with Member"`, ); }); diff --git a/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap b/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap index b9c507231c..6c3434ce72 100644 --- a/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap @@ -62,6 +62,7 @@ exports[` renders formatted m.text correctly pills appear for an data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 16px;" + title="" > renders formatted m.text correctly pills appear for eve data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 16px;" + title="" > renders formatted m.text correctly pills appear for roo data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 16px;" + title="" > renders formatted m.text correctly pills get injected c data-testid="avatar-img" data-type="round" style="--cpd-avatar-size: 16px;" + title="" > renders plain-text m.text correctly should pillify a pe href="https://matrix.to/#/!room1:example.com/%event_id%" aria-describedby="mx_Pill_0.123456" > diff --git a/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap b/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap index d193fbea78..888a42edae 100644 --- a/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap @@ -29,6 +29,7 @@ exports[` message case AskToJoin renders the corresponding mes data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > R @@ -54,6 +55,7 @@ exports[` message case AskToJoin renders the corresponding mes data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > ? @@ -220,6 +222,7 @@ exports[` with an invite with an invited email when client has data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > R @@ -278,6 +281,7 @@ exports[` with an invite without an invited email for a dm roo data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > R @@ -343,6 +347,7 @@ exports[` with an invite without an invited email for a non-dm data-type="round" role="presentation" style="--cpd-avatar-size: 36px;" + title="" > R diff --git a/test/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap b/test/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap index 0ece644a76..397d1cd221 100644 --- a/test/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap @@ -20,6 +20,7 @@ exports[`RoomTile when message previews are enabled and there is a message in a data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > ! @@ -94,6 +95,7 @@ exports[`RoomTile when message previews are enabled and there is a message in th data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > ! @@ -168,6 +170,7 @@ exports[`RoomTile when message previews are enabled should render a room without data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > ! @@ -230,6 +233,7 @@ exports[`RoomTile when message previews are not enabled should render the room 1 data-type="round" role="presentation" style="--cpd-avatar-size: 32px;" + title="" > ! diff --git a/test/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap b/test/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap index 1360328595..62791aea98 100644 --- a/test/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap +++ b/test/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap @@ -31,6 +31,7 @@ exports[` looks as expected 1`] = ` data-testid="avatar-img" data-type="square" style="--cpd-avatar-size: 40px;" + title="" >