From 646cb7b1246402caa54ddbcf84bcb9a7605dd7c0 Mon Sep 17 00:00:00 2001 From: heaven Date: Thu, 14 May 2026 21:11:36 +0300 Subject: [PATCH] feat(members): carve rounded TL/BL on members drawer with 12px void seam to chat and extract VoidGap helper consolidating four per-pane seams --- src/app/features/room/MembersDrawer.css.ts | 10 +++ src/app/features/room/Room.tsx | 76 ++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/app/features/room/MembersDrawer.css.ts b/src/app/features/room/MembersDrawer.css.ts index 860ceda0..e59ef7b9 100644 --- a/src/app/features/room/MembersDrawer.css.ts +++ b/src/app/features/room/MembersDrawer.css.ts @@ -1,8 +1,18 @@ import { keyframes, style } from '@vanilla-extract/css'; import { config, toRem } from 'folds'; +import { VOJO_HORSESHOE_RADIUS_PX } from '../../styles/horseshoe'; +// Left edge carves TL + BL the same way `RoomViewProfileSidePanel` does +// across the 12px horseshoe void gap rendered by Room.tsx — same design +// language as the page-nav <-> chat split. `overflow: hidden` keeps the +// rounded corners clean against header / scroll content; the void +// colour beneath is painted by the parent flex row, not by the panel +// itself. export const MembersDrawer = style({ width: toRem(266), + overflow: 'hidden', + borderTopLeftRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), + borderBottomLeftRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), }); export const MembersDrawerHeader = style({ diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index 32edb635..e12b00b9 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -38,6 +38,22 @@ type RoomProps = { renderRoomView?: (props: { eventId?: string }) => React.ReactNode; }; +// 12px black seam between the chat column and the right-side pane (profile, +// media, thread, members). Every active right-side pane renders one of +// these locally; the parent flex row is painted the same void colour by +// `Room` so each pane's TL/BL rounded carve can expose the void cleanly. +function VoidGap() { + return ( + + ); +} + export function Room({ renderRoomView }: RoomProps) { const { eventId } = useParams(); const room = useRoom(); @@ -115,18 +131,40 @@ export function Room({ renderRoomView }: RoomProps) { // parent void can't bleed through any transparent slivers. const profileOpen = !!useAtomValue(userRoomProfileAtom); const mediaOpen = !!useAtomValue(mediaViewerAtom); + const callView = room.isCallRoom(); const showProfileHorseshoe = profileOpen && !isMobile && !showThreadDrawer; // Media viewer side pane on desktop — same horseshoe seam idiom // as the profile pane. The two are mutually exclusive in practice // (the open hooks clear the other atom), so at most one shows at - // a time; both feed into `showAnyHorseshoe` for the chat-column - // bg + the void-gap render gate. + // a time; both feed into `paintParentVoid` for the chat-column bg + // and locally gate the shared `` seam. const showMediaHorseshoe = mediaOpen && !isMobile && !showThreadDrawer; // Thread drawer side pane on desktop. Mobile hides the chat column // entirely (`drawerHidesChat`) so the seam doesn't apply there. const showThreadHorseshoe = showThreadDrawer && !isMobile; - const showAnyHorseshoe = - showProfileHorseshoe || showMediaHorseshoe || showThreadHorseshoe; + // Members drawer side pane — mirrors the same horseshoe seam as the + // profile/media/thread panes. Gated on the exact same conditions that + // mount `` below (group room, desktop, drawer setting on, + // no thread overlay, no call surface) so the void gap appears iff the + // pane appears. + const showMembersHorseshoe = + !callView && + !isOneOnOne && + !showThreadDrawer && + screenSize === ScreenSize.Desktop && + isDrawer; + // True whenever any right-side pane is mounted. Drives the parent flex + // row's void background and the chat column's explicit Background paint + // — both prevent the chat-side surface from bleeding through the carved + // TL/BL of whichever pane is open. The per-pane `` decisions + // remain local to each render site (a single `paintParentVoid` gate + // would over-render when only members is open — there is no + // profile/media slot to anchor it to). + const paintParentVoid = + showProfileHorseshoe || + showMediaHorseshoe || + showThreadHorseshoe || + showMembersHorseshoe; useKeyDown( window, @@ -152,8 +190,6 @@ export function Room({ renderRoomView }: RoomProps) { ) ); - const callView = room.isCallRoom(); - // Disable the atom-driven media viewer when the desktop thread // drawer is open — the side-pane mount block below is gated on // `!showThreadDrawer`, so the new viewer's right pane wouldn't @@ -181,7 +217,7 @@ export function Room({ renderRoomView }: RoomProps) { - {showAnyHorseshoe && ( - - )} + {(showProfileHorseshoe || showMediaHorseshoe) && } @@ -277,21 +305,13 @@ export function Room({ renderRoomView }: RoomProps) { screenSize === ScreenSize.Desktop && isDrawer && ( <> - + {showMembersHorseshoe && } )} {showThreadDrawer && decodedRootId && parentRoomPath && ( <> - {showThreadHorseshoe && ( - - )} + {showThreadHorseshoe && }