diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx index ad8d1d76..39d9d289 100644 --- a/src/app/components/page/Page.tsx +++ b/src/app/components/page/Page.tsx @@ -62,28 +62,12 @@ export function PageRoot({ nav, children }: PageRootProps) { if (horseshoe) { return ( - {/* Page-nav slot — paints two void squares at its top-right and - bottom-right via `background-image` (the - `linear-gradient(SAME, SAME)` idiom = a solid-colour image). - The wrapper's bg sits naturally beneath its in-flow - children, so PageNavHeader and the bottom row paint their - normal Background colour over the void everywhere except in - their own rounded TR / BR carves — which expose the void. - No absolute positioning, no z-index gymnastics, and the - resize handle inside `ResizablePageNav` stays a sibling of - the clipped inner column so it isn't clipped. */} - - {nav} - + {/* Page-nav slot — its right edge stays square (the original + TR / BR rounding was reverted). The 12px void gap below + still creates a visible seam between page-nav and the chat + panel, and the resize handle inside `ResizablePageNav` is + shifted into that void via the inline style below. */} + {nav} ; // Web-only horseshoe shell wrapping every page-nav's inner column. -// `overflow:hidden + border-radius` clips the nav's content into a -// shape with rounded TR and BR corners; the wrapper PageRoot puts -// behind the nav paints `#090909` at those same corners via background- -// image, so the carved area reads as the horseshoe void. An explicit -// `Background.Container` bg is required: folds `
` is fully -// transparent (it only sets color/border), and some routes (Bots, -// Channels) don't paint anything at the bottom — without this bg the -// PageRoot void would bleed through the entire header / footer instead -// of just the carved corner. Applied conditionally in `PageNav` / -// `ResizablePageNav` below; native skips it entirely. +// Previously carved rounded TR / BR corners against a void backdrop; +// the rounding was reverted while keeping the 12px void gap between +// page-nav and chat panel, so this class now just enforces an opaque +// Background bg + clips overflow. The bg keeps the page-nav header / +// footer painted even on routes (Bots, Channels) that don't paint +// anything of their own; `overflow:hidden` is defensive against any +// child that might bleed past the inner column. Applied conditionally +// in `PageNav` / `ResizablePageNav` below; native skips it entirely. export const PageNavInnerWebHorseshoe = style({ overflow: 'hidden', - borderTopRightRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), - borderBottomRightRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), backgroundColor: color.Background.Container, }); diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index f075ba5b..9aa266a5 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -1,9 +1,12 @@ import React, { useCallback } from 'react'; -import { Box, Line } from 'folds'; +import { Box, Line, toRem } from 'folds'; import { useMatch, useParams } from 'react-router-dom'; import { isKeyHotkey } from 'is-hotkey'; import { useAtomValue } from 'jotai'; import { RoomView } from './RoomView'; +import { userRoomProfileAtom } from '../../state/userRoomProfile'; +import { ContainerColor } from '../../styles/ContainerColor.css'; +import { VOJO_HORSESHOE_GAP_PX, VOJO_HORSESHOE_VOID_COLOR } from '../../styles/horseshoe'; import { MembersDrawer } from './MembersDrawer'; import { ThreadDrawer } from './ThreadDrawer'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; @@ -91,6 +94,24 @@ export function Room({ renderRoomView }: RoomProps) { const unreadThreadingEnabled = useUnreadThreadingEnabled(); + // Profile horseshoe: when the right-side profile pane is open on + // desktop/tablet, paint a 12px void seam between the chat column and + // the profile, and carve rounded TL/BL corners on the profile pane. + // The chat column itself keeps a straight right edge (no rounding) — + // only the profile carves, so the void gap reads as one black bar + // with a single rounded contour on the right side of the seam. + // Mirrors the page-nav <-> chat split from PageRoot (commit 363bd9d) + // minus the chat-side rounding. Mobile and the thread-drawer case + // don't mount the side pane, so the wrap is gated on both. + // + // Void colour is painted on the parent flex row (covering everything + // not painted by an opaque child) so the profile pane's TL/BL carves + // expose void rather than the chat-panel-inner's Background from + // PageRoot. The chat column applies an explicit Background bg so the + // parent void can't bleed through any transparent slivers. + const profileOpen = !!useAtomValue(userRoomProfileAtom); + const showProfileHorseshoe = profileOpen && !isMobile && !showThreadDrawer; + useKeyDown( window, useCallback( @@ -120,9 +141,22 @@ export function Room({ renderRoomView }: RoomProps) { return ( - + {callView && (screenSize === ScreenSize.Desktop || !chat) && ( - + }> @@ -131,7 +165,13 @@ export function Room({ renderRoomView }: RoomProps) { )} {!callView && !drawerHidesChat && ( - + }> {renderRoomView?.({ eventId }) ?? } @@ -143,8 +183,23 @@ export function Room({ renderRoomView }: RoomProps) { {/* Tablet / Desktop: profile renders as a third pane to the right of the chat. Mobile uses the top horseshoe inside `RoomViewProfilePanel`, so we don't mount the side pane - there. */} - {!isMobile && !showThreadDrawer && } + there. The 12px void gap (same as page-nav <-> chat split + from PageRoot) sits between the chat column and the pane + so the seam reads identically to the rest of the app. */} + {!isMobile && !showThreadDrawer && ( + <> + {showProfileHorseshoe && ( + + )} + + + )} {callView && chat && ( <> diff --git a/src/app/features/room/RoomViewProfileSidePanel.css.ts b/src/app/features/room/RoomViewProfileSidePanel.css.ts index 96c6578f..d7e14933 100644 --- a/src/app/features/room/RoomViewProfileSidePanel.css.ts +++ b/src/app/features/room/RoomViewProfileSidePanel.css.ts @@ -1,18 +1,28 @@ import { style } from '@vanilla-extract/css'; import { color, config, toRem } from 'folds'; +import { VOJO_HORSESHOE_RADIUS_PX } from '../../styles/horseshoe'; // Right-side profile pane sized like the members drawer family — // wide enough for the identity card + chips without dominating the // chat. Clamp keeps it readable on narrow desktops and prevents // runaway width on ultra-wide displays. +// +// Left edge is rounded (TL + BL) to mirror the chat column's TR / BR +// carves 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 the header / +// scroll content; the void colour beneath is painted by the parent +// flex row, not by the panel itself. export const panel = style({ flexShrink: 0, width: `clamp(${toRem(300)}, 25%, ${toRem(380)})`, display: 'flex', flexDirection: 'column', backgroundColor: color.Surface.Container, - borderLeft: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`, minHeight: 0, + overflow: 'hidden', + borderTopLeftRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), + borderBottomLeftRadius: toRem(VOJO_HORSESHOE_RADIUS_PX), }); // Match the chat header's left gutter (RoomViewHeaderDm uses S200