From b818d3fc5a3f40f5a699ff919a4b827d3a17cbe9 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 19 Jun 2026 13:07:51 -0400 Subject: [PATCH] refactor(media-gallery): redesign chrome to match native folds conventions The Media Gallery panel worked but didn't look like a first-party Cinny drawer. Redesign the chrome to match MembersDrawer / Saved Messages and the PolicyListViewer tab precedent: - panel + header: Surface -> Background variant; header uses Text size="H4" and a plain close IconButton (dropped the bespoke tooltip-wrapped button) - tabs: moved into a bordered toolbar strip; adopt the repo's variant={active?'Primary':'Secondary'} fill={active?'Solid':'Soft'} pattern and show per-tab counts (Images (N) / Videos (N) / Files (N)) - month grouping: replaced the centered "lines + label" divider with a left-aligned group label (the Cinny group-label pattern) - thumbnail tiles: hover/focus border + caption overlay are now CSS-driven (:hover / :focus-visible) instead of React state, and live in MediaGallery.css.ts; grid + file rows tokenized - caption overlay also reveals on keyboard focus (a11y) All styling consolidated into MediaGallery.css.ts; no inline grid/tile styles. Co-Authored-By: Claude Opus 4.8 --- LOTUS_BUGS.md | 48 +++--- src/app/features/room/MediaGallery.css.ts | 101 +++++++++++- src/app/features/room/MediaGallery.tsx | 189 +++++++--------------- 3 files changed, 178 insertions(+), 160 deletions(-) diff --git a/LOTUS_BUGS.md b/LOTUS_BUGS.md index e8c2976be..c486ae5fc 100644 --- a/LOTUS_BUGS.md +++ b/LOTUS_BUGS.md @@ -307,30 +307,30 @@ This document tracks identified bugs, edge cases, and architectural discrepancie ### 🟠 Moderate β€” Interaction Pattern or Visual Deviations -| # | Area | File | Lines | Issue | Native Pattern | -| :-- | :------------------------- | :---------------------------------------- | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| N5 | Read Receipts | `ReadReceiptAvatars.tsx` | 62–137 | Trigger button is raw ` ); } @@ -657,6 +589,25 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { }; }); + // Per-tab counts for the tab labels (single pass over loaded timeline) + const tabCounts = useMemo(() => { + const counts: Record = { image: 0, video: 0, file: 0 }; + room + .getLiveTimeline() + .getEvents() + .forEach((ev) => { + if (ev.isRedacted() || ev.getType() !== EventType.RoomMessage) return; + const mt = ev.getContent().msgtype; + if (mt === MsgType.Image) counts.image += 1; + else if (mt === MsgType.Video) counts.video += 1; + else if (mt === MsgType.File) counts.file += 1; + }); + return counts; + // `events` is intentional: it changes when more history is paginated in, so + // the counts stay in sync with the loaded window (it isn't read directly). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [room, events]); + // Group image/video events by month for the grid type MonthGroup = { label: string; events: MatrixEvent[] }; const monthGroups: MonthGroup[] = []; @@ -673,52 +624,31 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { return ( <> {/* Header */} -
+
- + Media Gallery - {events.length > 0 && ( - - {events.length} - - )} - - Close - - } - > - {(ref) => ( - - - - )} - + + +
{/* Tabs */} - + {(Object.keys(TAB_LABELS) as GalleryTab[]).map((t) => ( handleTabChange(t)} /> @@ -728,7 +658,7 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { {/* Content */} - + {/* ── Image / video grid ── */} {(tab === 'image' || tab === 'video') && ( <> @@ -752,20 +682,14 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { {(() => { let flatIdx = 0; return monthGroups.map((group) => ( - - {/* Month header β€” only shown when there are multiple groups */} - {monthGroups.length > 1 && } -
+ + {/* Month label β€” only shown when there are multiple groups */} + {monthGroups.length > 1 && ( + + {group.label} + + )} +
{group.events.map((mEvent) => { const c = mEvent.getContent(); const isEnc = !!c.file; @@ -843,8 +767,7 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { style={{ padding: `${config.space.S200} ${config.space.S300}`, borderRadius: config.radii.R300, - border: `1px solid ${color.Surface.ContainerLine}`, - background: color.Surface.Container, + background: color.SurfaceVariant.Container, }} > @@ -862,7 +785,7 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {