From ead1290ac452ec3d4f6631b61f145f5a4e36b3b7 Mon Sep 17 00:00:00 2001 From: "v.lagerev" Date: Sun, 3 May 2026 14:48:27 +0300 Subject: [PATCH] feat(ui): force every user and room avatar to render as a circle via globalStyle override on the folds Avatar wrapper --- src/app/components/room-avatar/RoomAvatar.css.ts | 9 ++++++++- src/app/components/room-avatar/RoomAvatar.tsx | 3 +++ src/app/components/user-avatar/UserAvatar.css.ts | 13 ++++++++++++- src/app/components/user-avatar/UserAvatar.tsx | 3 +++ src/app/features/bots/BotCard.tsx | 2 +- src/app/features/bots/BotShell.css.ts | 15 +++++++-------- 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/app/components/room-avatar/RoomAvatar.css.ts b/src/app/components/room-avatar/RoomAvatar.css.ts index c9745a9a..6202b7fc 100644 --- a/src/app/components/room-avatar/RoomAvatar.css.ts +++ b/src/app/components/room-avatar/RoomAvatar.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css'; +import { globalStyle, style } from '@vanilla-extract/css'; import { color } from 'folds'; export const RoomAvatar = style({ @@ -12,3 +12,10 @@ export const RoomAvatar = style({ }, }, }); + +// See `UserAvatar.css.ts` for the rationale — same one-liner override forces +// every RoomAvatar (rooms, spaces, DMs, bridged puppets) into a circle without +// touching callsites. +globalStyle(`*:has(> .${RoomAvatar})`, { + borderRadius: '50% !important', +}); diff --git a/src/app/components/room-avatar/RoomAvatar.tsx b/src/app/components/room-avatar/RoomAvatar.tsx index cbcd626a..60d76175 100644 --- a/src/app/components/room-avatar/RoomAvatar.tsx +++ b/src/app/components/room-avatar/RoomAvatar.tsx @@ -11,6 +11,9 @@ type RoomAvatarProps = { alt?: string; renderFallback: () => ReactNode; }; +// Always renders as a circle: a globalStyle in `RoomAvatar.css.ts` forces the +// parent folds `` to `border-radius: 50%`, so the `radii` prop on the +// outer `` is intentionally inert at every callsite. export function RoomAvatar({ roomId, src, alt, renderFallback }: RoomAvatarProps) { const [error, setError] = useState(false); diff --git a/src/app/components/user-avatar/UserAvatar.css.ts b/src/app/components/user-avatar/UserAvatar.css.ts index 0a3684b2..34283d73 100644 --- a/src/app/components/user-avatar/UserAvatar.css.ts +++ b/src/app/components/user-avatar/UserAvatar.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css'; +import { globalStyle, style } from '@vanilla-extract/css'; import { color } from 'folds'; export const UserAvatar = style({ @@ -12,3 +12,14 @@ export const UserAvatar = style({ }, }, }); + +// Force every UserAvatar to render as a circle. Folds `` controls the +// outer shape via its `radii` variant; the inner `AvatarImage`/`AvatarFallback` +// inherit `border-radius` from it. By overriding the parent's radius we round +// every user avatar without touching the ~30 callsites or losing the radii +// system for non-avatar uses of folds `` (icon tabs, emoji-pack tiles). +// `!important` is necessary because folds' recipe rule has the same specificity +// and source-order would otherwise be a coin flip. +globalStyle(`*:has(> .${UserAvatar})`, { + borderRadius: '50% !important', +}); diff --git a/src/app/components/user-avatar/UserAvatar.tsx b/src/app/components/user-avatar/UserAvatar.tsx index d9de9b79..3df80624 100644 --- a/src/app/components/user-avatar/UserAvatar.tsx +++ b/src/app/components/user-avatar/UserAvatar.tsx @@ -11,6 +11,9 @@ type UserAvatarProps = { alt?: string; renderFallback: () => ReactNode; }; +// Always renders as a circle: a globalStyle in `UserAvatar.css.ts` forces the +// parent folds `` to `border-radius: 50%`, so the `radii` prop on the +// outer `` is intentionally inert at every callsite. export function UserAvatar({ className, userId, src, alt, renderFallback }: UserAvatarProps) { const [error, setError] = useState(false); diff --git a/src/app/features/bots/BotCard.tsx b/src/app/features/bots/BotCard.tsx index a2b95e78..db691f91 100644 --- a/src/app/features/bots/BotCard.tsx +++ b/src/app/features/bots/BotCard.tsx @@ -53,7 +53,7 @@ export function BotCard({ preset, selected }: BotCardProps) { padding: `${toRem(6)} 0`, }} > - + {avatarUrl ? ( ) : ( diff --git a/src/app/features/bots/BotShell.css.ts b/src/app/features/bots/BotShell.css.ts index 7f6f5edd..0949bf82 100644 --- a/src/app/features/bots/BotShell.css.ts +++ b/src/app/features/bots/BotShell.css.ts @@ -97,18 +97,18 @@ export const HeroBack = style([ }, ]); -// 56×56 square avatar with 14px radius, fleet violet (DAWN.fleet) bg. -// Fleet color is hardcoded here because it's the canonical bot accent in -// the mockup and we don't want it varying with Folds palette swaps. The -// violet square shows when the bot's Matrix profile has no `avatar_url` -// (fallback to the initial letter); when it does, the inner covers -// the violet — `overflow: hidden` keeps it inside the rounded corners. +// 56×56 circular avatar, fleet violet (DAWN.fleet) bg. Fleet color is +// hardcoded here because it's the canonical bot accent in the mockup and we +// don't want it varying with Folds palette swaps. The violet disk shows when +// the bot's Matrix profile has no `avatar_url` (fallback to the initial +// letter); when it does, the inner covers the violet — `overflow: +// hidden` keeps it inside the round mask. export const HeroAvatar = style([ DefaultReset, { width: toRem(56), height: toRem(56), - borderRadius: toRem(14), + borderRadius: '50%', backgroundColor: '#9580ff', color: '#0c0c0e', fontSize: toRem(24), @@ -123,7 +123,6 @@ export const HeroAvatar = style([ '(max-width: 600px)': { width: toRem(36), height: toRem(36), - borderRadius: toRem(8), fontSize: toRem(16), }, },