From 53515ea4275e0529ede5859e3499bbed1382d39d Mon Sep 17 00:00:00 2001 From: heaven Date: Mon, 15 Jun 2026 02:57:32 +0300 Subject: [PATCH] style(message): tighten channel layout and channels list for mobile touch and rework the jump-to-message highlight --- .../components/message/layout/Channel.css.ts | 66 ++++++++++++++----- src/app/components/message/layout/Channel.tsx | 13 +++- .../components/message/layout/layout.css.ts | 27 ++++++++ .../pages/client/channels/ChannelsList.css.ts | 38 +++++++---- 4 files changed, 114 insertions(+), 30 deletions(-) diff --git a/src/app/components/message/layout/Channel.css.ts b/src/app/components/message/layout/Channel.css.ts index 85776a32..0fafa9f9 100644 --- a/src/app/components/message/layout/Channel.css.ts +++ b/src/app/components/message/layout/Channel.css.ts @@ -1,24 +1,39 @@ -import { globalStyle, style } from '@vanilla-extract/css'; +import { createVar, globalStyle, style } from '@vanilla-extract/css'; import { color, config, toRem } from 'folds'; -// 40px circular avatar — Discord's cozy-mode avatar size. Consumers override -// the folds preset via inline style; the shared `CHANNEL_AVATAR_PX` constant -// keeps the CSS slot width and the inline override in sync. -export const CHANNEL_AVATAR_PX = 40; -const ChannelAvatarWidth = toRem(CHANNEL_AVATAR_PX); +// Avatar diameter — Discord's cozy-mode 40px on desktop, compacted to 32px +// on narrow/native viewports (the 40px slot + 16px gutters wasted ~a third +// of a 360px screen on chrome — user request, redesign item 11). Exposed as +// a CSS var scoped on ChannelRow so the inline `` size override in +// Channel.tsx tracks the same breakpoint as the slot width without JS +// media-query plumbing. +export const CHANNEL_AVATAR_SIZE_VAR = createVar(); +const ChannelAvatarWidth = CHANNEL_AVATAR_SIZE_VAR; -// Discord cozy-mode geometry: avatar 16px from the list edge, 16px gap to the -// content, so the message column starts at 16 + 40 + 16 = 72px. +// Mirrors BubbleTimelineBand's desktop breakpoint (RoomTimeline.css.ts) — +// keep the two media queries in sync. +const MOBILE_MEDIA = 'screen and (max-width: 599px)'; + +// Discord cozy-mode geometry on desktop: avatar 16px from the list edge, +// 16px gap to the content, so the message column starts at 16 + 40 + 16 = +// 72px. On mobile everything tightens: 6px edge, 32px avatar, 10px gap → +// body column at 48px from the band edge (60px from the screen edge with +// the band's 12px native gutter), vs the old 84px. const ChannelEdgePad = toRem(16); const ChannelAvatarGap = toRem(16); +const ChannelEdgePadMobile = toRem(6); +const ChannelAvatarGapMobile = toRem(10); export const ChannelRow = style({ display: 'flex', alignItems: 'flex-start', gap: ChannelAvatarGap, + vars: { + [CHANNEL_AVATAR_SIZE_VAR]: toRem(40), + }, // Span the full message-column width so the hover highlight runs edge-to-edge // like Discord: cancel MessageBase's S400/S200 horizontal padding with negative - // margins, then re-add the 16px avatar gutter as paddingLeft (so the avatar's + // margins, then re-add the avatar gutter as paddingLeft (so the avatar's // left edge lands 16px from the column edge — Discord cozy). NB: the column is // the centred BubbleTimelineBand, so that edge is the band's content edge, not // the screen edge (the band adds 12px native / 40px desktop outside this). @@ -31,9 +46,9 @@ export const ChannelRow = style({ paddingTop: toRem(2), paddingBottom: toRem(2), minWidth: 0, - // Hover bg subtle so adjacent rows still read as distinct units. `@media - // (hover: hover)` keeps this inert on touch where there's no pointer. '@media': { + // Hover bg subtle so adjacent rows still read as distinct units. `@media + // (hover: hover)` keeps this inert on touch where there's no pointer. '(hover: hover) and (pointer: fine)': { selectors: { '&:hover': { @@ -41,12 +56,20 @@ export const ChannelRow = style({ }, }, }, + [MOBILE_MEDIA]: { + vars: { + [CHANNEL_AVATAR_SIZE_VAR]: toRem(32), + }, + gap: ChannelAvatarGapMobile, + paddingLeft: ChannelEdgePadMobile, + paddingRight: toRem(8), + }, }, }); // Fixed-width slot keeps the body column aligned across collapsed rows -// (where `avatar` is `undefined` — the slot still occupies `ChannelAvatarWidth`, -// so the body's left edge stays put). +// (where `avatar` is `undefined` — the slot still occupies the avatar +// diameter, so the body's left edge stays put). export const ChannelAvatarSlot = style({ width: ChannelAvatarWidth, flexShrink: 0, @@ -90,14 +113,23 @@ export const ChannelThreadSummary = style({ // continuous. export const ChannelSysline = style({ // Indent past the avatar gutter so the sysline body aligns with the message - // body column (72px). The sysline sits inside MessageBase's S400 (16px) left - // pad (it has no edge-to-edge negative margin), so paddingLeft = avatar (40) - // + gap (16) = 56 lands the content at 16 + 56 = 72px. - paddingLeft: `calc(${ChannelAvatarWidth} + ${ChannelAvatarGap})`, + // body column (72px desktop). The sysline sits inside MessageBase's S400 + // (16px) left pad (it has no edge-to-edge negative margin), so paddingLeft + // = avatar (40) + gap (16) = 56 lands the content at 16 + 56 = 72px. + // ChannelRow's avatar-size var is out of scope here (sysline rows render + // standalone), so the mobile compaction repeats the literals: 32 + 10 = 42, + // minus the 10px edge-pad difference (16 − 6) the message rows gained, + // = 32px — keeps the sysline body on the message-body column line. + paddingLeft: `calc(${toRem(40)} + ${ChannelAvatarGap})`, paddingRight: config.space.S200, paddingTop: config.space.S100, paddingBottom: config.space.S100, color: color.SurfaceVariant.OnContainer, + '@media': { + [MOBILE_MEDIA]: { + paddingLeft: toRem(32), + }, + }, }); export const ChannelSyslineIcon = style({ diff --git a/src/app/components/message/layout/Channel.tsx b/src/app/components/message/layout/Channel.tsx index e600cc53..141ab3c2 100644 --- a/src/app/components/message/layout/Channel.tsx +++ b/src/app/components/message/layout/Channel.tsx @@ -4,7 +4,7 @@ import { Avatar, Box, Icon, Icons, type IconSrc, as } from 'folds'; import { type Room } from 'matrix-js-sdk'; import * as css from './Channel.css'; -import { CHANNEL_AVATAR_PX } from './Channel.css'; +import { CHANNEL_AVATAR_SIZE_VAR } from './Channel.css'; import { UserAvatar } from '../../user-avatar'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; @@ -137,7 +137,16 @@ export function ChannelMessageAvatar({ ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; return ( - + // Size from the ChannelRow-scoped CSS var (40px desktop / 32px mobile) so + // the avatar tracks the slot's breakpoint while still out-ranking the + // folds size preset via inline style. +