From a3dd873d3669f421e9f90c291aa241075c449cd4 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 30 May 2026 23:52:15 -0400 Subject: [PATCH] fix: restore avatar rendering and split presence dot into separate button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous version wrapped UserAvatar in a div inside SidebarAvatar, which broke the folds Avatar CSS (expects AvatarImage/AvatarFallback as direct child) — causing the white circle instead of the avatar. New approach: - SidebarAvatar has only UserAvatar as its direct child (restored) - Clicking the avatar opens Settings directly (original behavior) - PresencePicker renders a small absolutely-positioned button in the bottom-right corner of SidebarItem (which already has position:relative) - Clicking the presence dot opens the status picker menu Co-Authored-By: Claude Sonnet 4.6 --- src/app/pages/client/sidebar/SettingsTab.tsx | 209 +++++++++---------- 1 file changed, 100 insertions(+), 109 deletions(-) diff --git a/src/app/pages/client/sidebar/SettingsTab.tsx b/src/app/pages/client/sidebar/SettingsTab.tsx index 50050f205..30bc05e62 100644 --- a/src/app/pages/client/sidebar/SettingsTab.tsx +++ b/src/app/pages/client/sidebar/SettingsTab.tsx @@ -63,14 +63,97 @@ function presenceVariant(status: string): 'Success' | 'Warning' | 'Critical' | ' return 'Secondary'; } +function PresencePicker() { + const [presenceStatus, setPresenceStatus] = useSetting(settingsAtom, 'presenceStatus'); + const [menuAnchor, setMenuAnchor] = useState(); + + const currentOption = + PRESENCE_OPTIONS.find((o) => o.id === presenceStatus) ?? PRESENCE_OPTIONS[4]; + const closeMenu = () => setMenuAnchor(undefined); + + return ( + + + + + + Set Status + + + {PRESENCE_OPTIONS.map((option) => ( + } + after={ + option.id === presenceStatus ? ( + + ) : undefined + } + onClick={() => { + setPresenceStatus(option.id); + closeMenu(); + }} + > + {option.label} + + ))} + + + + } + > + {/* Presence dot sits in the bottom-right corner of SidebarItem (which is position:relative) */} + + + ); +} + export function SettingsTab() { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const userId = mx.getUserId()!; const profile = useUserProfile(userId); - const [presenceStatus, setPresenceStatus] = useSetting(settingsAtom, 'presenceStatus'); - const [menuAnchor, setMenuAnchor] = useState(); const [settingsOpen, setSettingsOpen] = useState(false); const displayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId; @@ -78,114 +161,22 @@ export function SettingsTab() { ? (mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined) : undefined; - const currentOption = - PRESENCE_OPTIONS.find((o) => o.id === presenceStatus) ?? PRESENCE_OPTIONS[4]; - - const closeMenu = () => setMenuAnchor(undefined); - return ( - - - - - - - Set Status - - - {PRESENCE_OPTIONS.map((option) => ( - } - after={ - option.id === presenceStatus ? ( - - ) : undefined - } - onClick={() => { - setPresenceStatus(option.id); - closeMenu(); - }} - > - {option.label} - - ))} -
- } - onClick={() => { - closeMenu(); - setSettingsOpen(true); - }} - > - User Settings - - -
- - } - > - - {(triggerRef) => ( - ) => - setMenuAnchor(e.currentTarget.getBoundingClientRect()) - } - > -
- {nameInitials(displayName)}} - /> -
- -
-
-
- )} -
-
+ // SidebarItem already has position:relative in its CSS — the PresencePicker + // button is absolutely positioned inside it, below the avatar. + + + {(triggerRef) => ( + setSettingsOpen(true)}> + {nameInitials(displayName)}} + /> + + )} + + {settingsOpen && ( setSettingsOpen(false)}> setSettingsOpen(false)} />