feat(room): extend the centred message band, date pill and composer width to group and channel rooms, matching 1:1
This commit is contained in:
parent
4b7ad11620
commit
2581ff8137
6 changed files with 42 additions and 126 deletions
|
|
@ -16,10 +16,12 @@ export const ChannelRow = style({
|
|||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: ChannelAvatarGap,
|
||||
// Span the full pane edge-to-edge so the hover highlight runs the whole
|
||||
// width 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 left edge lands exactly 16px from the screen edge — Discord cozy).
|
||||
// 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
|
||||
// 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).
|
||||
marginLeft: `calc(-1 * ${config.space.S400})`,
|
||||
marginRight: `calc(-1 * ${config.space.S200})`,
|
||||
paddingLeft: ChannelEdgePad,
|
||||
|
|
@ -82,35 +84,6 @@ export const ChannelThreadSummary = style({
|
|||
marginTop: config.space.S100,
|
||||
});
|
||||
|
||||
// Horizontal line + centered label. Spans the full row width including
|
||||
// the avatar slot so the line reads as a section break, not a per-message
|
||||
// chrome element.
|
||||
export const ChannelDayDividerRoot = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: config.space.S300,
|
||||
paddingLeft: config.space.S400,
|
||||
paddingRight: config.space.S400,
|
||||
paddingTop: config.space.S400,
|
||||
paddingBottom: config.space.S400,
|
||||
});
|
||||
|
||||
export const ChannelDayDividerLine = style({
|
||||
flex: 1,
|
||||
height: '1px',
|
||||
backgroundColor: color.SurfaceVariant.ContainerLine,
|
||||
});
|
||||
|
||||
export const ChannelDayDividerLabel = style({
|
||||
fontSize: toRem(11),
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.06em',
|
||||
textTransform: 'uppercase',
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
opacity: 0.7,
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
// Sysline (membership / room.create / pinned-events). Compact single
|
||||
// row aligned with the body gutter — the sysline sits where messages'
|
||||
// body would, indented past the avatar slot, so the column reads
|
||||
|
|
|
|||
|
|
@ -98,29 +98,6 @@ export const ChannelLayout = as<'div', ChannelLayoutProps>(
|
|||
)
|
||||
);
|
||||
|
||||
export type ChannelDayDividerProps = {
|
||||
label: ReactNode;
|
||||
};
|
||||
|
||||
// Section break between days in the channels timeline. Horizontal line
|
||||
// + centered uppercase label, spans full row including the avatar gutter
|
||||
// so it reads as a structural separator.
|
||||
export const ChannelDayDivider = as<'div', ChannelDayDividerProps>(
|
||||
({ className, label, ...props }, ref) => (
|
||||
<div
|
||||
className={classNames(css.ChannelDayDividerRoot, className)}
|
||||
role="separator"
|
||||
aria-label={typeof label === 'string' ? label : undefined}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<span className={css.ChannelDayDividerLine} aria-hidden />
|
||||
<span className={css.ChannelDayDividerLabel}>{label}</span>
|
||||
<span className={css.ChannelDayDividerLine} aria-hidden />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export type ChannelEventContentProps = {
|
||||
iconSrc: IconSrc;
|
||||
content: ReactNode;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import {
|
|||
VOJO_STICKY_DATE_TOP_PX,
|
||||
} from '../../styles/horseshoe';
|
||||
|
||||
// Bubble (1:1 DM) timeline band — centre the message column in the same band
|
||||
// Timeline band (every room class — 1:1 bubble, group, channel) — centre the
|
||||
// message column in the same band
|
||||
// the AI-bot chat uses (ThreadDrawerContentAssistant), so on wide web viewports
|
||||
// the chat is centred instead of spreading edge-to-edge. Inert on mobile (band
|
||||
// > viewport). The composer mirrors this via ComposerBubbleBand; both read the
|
||||
|
|
@ -32,7 +33,8 @@ export const BubbleTimelineBand = style({
|
|||
},
|
||||
});
|
||||
|
||||
// Day capsule for the bubble (1:1 DM) timeline («Среда, 4 июня» / «Сегодня») —
|
||||
// Day capsule for the timeline («Среда, 4 июня» / «Сегодня»), shared by every
|
||||
// room class (1:1 bubble, group, channel) —
|
||||
// a single dark-blue pill in the message-input tone (Surface.Container, the same
|
||||
// token the composer card paints with), centred, with generous rounding. No
|
||||
// border, no echo.
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ import {
|
|||
MSticker,
|
||||
ImageContent,
|
||||
EventContent,
|
||||
CHANNEL_MESSAGE_SPACING,
|
||||
ChannelDayDivider,
|
||||
} from '../../components/message';
|
||||
import {
|
||||
factoryRenderLinkifyWithMention,
|
||||
|
|
@ -1302,7 +1300,8 @@ export function RoomTimeline({
|
|||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Sticky day capsules (bubble layout only). Each day boundary renders a REAL
|
||||
// Sticky day capsules (every room class — 1:1 bubble, group, channel). Each
|
||||
// day boundary renders a REAL
|
||||
// capsule that is a CSS `position: sticky` element (see RoomTimeline.css
|
||||
// `[data-sticky-dates='on'] BubbleDayCapsuleRow`). The browser pins it on the
|
||||
// compositor while you scroll through a day, then lets it settle back into its
|
||||
|
|
@ -1319,7 +1318,6 @@ export function RoomTimeline({
|
|||
// `offsetTop` is the in-flow position (sticky doesn't change it), so the
|
||||
// front detection and the virtual paginator's offsetTop maths stay in agreement.
|
||||
useEffect(() => {
|
||||
if (messageLayout === 'channel') return undefined;
|
||||
const scrollEl = getScrollElement();
|
||||
if (!scrollEl) return undefined;
|
||||
|
||||
|
|
@ -1370,7 +1368,7 @@ export function RoomTimeline({
|
|||
delete scrollEl.dataset.stickyDates;
|
||||
showAll();
|
||||
};
|
||||
}, [getScrollElement, messageLayout]);
|
||||
}, [getScrollElement]);
|
||||
|
||||
// Group `m.call.member` (StateEvent.GroupCallMemberPrefix) events into one
|
||||
// aggregate bubble per CALL SESSION. Each session is delimited by «joined
|
||||
|
|
@ -2635,18 +2633,13 @@ export function RoomTimeline({
|
|||
return timeDayMonYear(mEvent.getTs());
|
||||
})();
|
||||
|
||||
const renderDayDivider = () =>
|
||||
messageLayout === 'channel' ? (
|
||||
<MessageBase space={CHANNEL_MESSAGE_SPACING}>
|
||||
<ChannelDayDivider label={dayLabel} />
|
||||
</MessageBase>
|
||||
) : (
|
||||
// Bubble (1:1 DM): a centred, single dark-blue date pill. The row is the
|
||||
// real `position: sticky` element — `data-day-divider` is the hook the
|
||||
// scroll effect uses to engage stickiness and pick the front pill when
|
||||
// several pile up. No MessageBase wrapper: the row must be a direct child
|
||||
// of the timeline column so its sticky containing block is the whole day,
|
||||
// not a one-row box.
|
||||
const renderDayDivider = () => (
|
||||
// Every room class (1:1 bubble, group, channel) draws the same centred,
|
||||
// single dark-blue date pill. The row is the real `position: sticky`
|
||||
// element — `data-day-divider` is the hook the scroll effect uses to engage
|
||||
// stickiness and pick the front pill when several pile up. No MessageBase
|
||||
// wrapper: the row must be a direct child of the timeline column so its
|
||||
// sticky containing block is the whole day, not a one-row box.
|
||||
<div className={css.BubbleDayCapsuleRow} data-day-divider="true">
|
||||
<span className={css.BubbleDayCapsule} role="separator">
|
||||
{dayLabel}
|
||||
|
|
@ -2673,7 +2666,7 @@ export function RoomTimeline({
|
|||
|
||||
return (
|
||||
<Box grow="Yes" style={{ position: 'relative' }}>
|
||||
{/* Bubble (1:1 DM) day dates are the inline capsules themselves, made
|
||||
{/* Day dates are the inline capsules themselves (every room class), made
|
||||
sticky via real CSS `position: sticky` (engaged by the effect above) —
|
||||
no separate floating pill. */}
|
||||
{unreadFloatShown && (
|
||||
|
|
@ -2703,8 +2696,8 @@ export function RoomTimeline({
|
|||
<Box
|
||||
direction="Column"
|
||||
justifyContent="End"
|
||||
// Bubble (1:1 DM) layout: centre the message column in the AI-chat band.
|
||||
className={messageLayout === 'stream' ? css.BubbleTimelineBand : undefined}
|
||||
// Every room class centres its message column in the AI-chat band.
|
||||
className={css.BubbleTimelineBand}
|
||||
style={{
|
||||
minHeight: '100%',
|
||||
// Bottom padding reserves room for the overlay composer painted
|
||||
|
|
|
|||
|
|
@ -24,22 +24,11 @@ import {
|
|||
// same Android-WebView stuck-:hover suppression.
|
||||
export const ChatComposer = style({});
|
||||
|
||||
// Desktop web only: constrain the composer card to ~3/4 of the chat pane
|
||||
// width and centre it, instead of spanning edge-to-edge (user point 15).
|
||||
// Applied conditionally in RoomView.tsx via `useScreenSizeContext` so native
|
||||
// / mobile / tablet keep the full-width card the user is happy with.
|
||||
export const ComposerDesktopClamp = style({
|
||||
maxWidth: '75%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
});
|
||||
|
||||
// Bubble (1:1 DM) composer band — fit the input form to the same ~960px centred
|
||||
// band as the bubble timeline + the AI-bot chat (ThreadComposerAssistant), so on
|
||||
// wide web the composer width matches the messages. Horizontal gutter matches
|
||||
// BubbleTimelineBand / the bot chat: 12px on native, 40px on desktop, so the
|
||||
// composer stays aligned with the message column. Replaces ComposerDesktopClamp
|
||||
// for 1:1 DMs.
|
||||
// Composer band (every room class) — fit the input form to the same ~960px
|
||||
// centred band as the timeline (BubbleTimelineBand) + the AI-bot chat
|
||||
// (ThreadComposerAssistant), so on wide web the composer width matches the
|
||||
// messages. Horizontal gutter matches BubbleTimelineBand / the bot chat: 12px on
|
||||
// native, 40px on desktop, so the composer stays aligned with the message column.
|
||||
export const ComposerBubbleBand = style({
|
||||
width: '100%',
|
||||
maxWidth: toRem(VOJO_BUBBLE_BAND_PX),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Box, Text, color, config, toRem } from 'folds';
|
||||
import { Box, Text, color, config } from 'folds';
|
||||
import { EventType } from 'matrix-js-sdk';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import classNames from 'classnames';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { useStateEvent } from '../../hooks/useStateEvent';
|
||||
import { StateEvent } from '../../../types/matrix/room';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
|
|
@ -19,9 +18,8 @@ import { useKeyDown } from '../../hooks/useKeyDown';
|
|||
import { editableActiveElement } from '../../utils/dom';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useIsOneOnOne, useRoom } from '../../hooks/useRoom';
|
||||
import { useChannelsMode, useThreadDrawerOpen } from '../../hooks/useChannelsMode';
|
||||
import { VOJO_HORSESHOE_GAP_PX } from '../../styles/horseshoe';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
import { useThreadDrawerOpen } from '../../hooks/useChannelsMode';
|
||||
import * as css from './RoomView.css';
|
||||
|
||||
const FN_KEYS_REGEX = /^F\d+$/;
|
||||
|
|
@ -91,16 +89,6 @@ export function RoomView({ eventId }: { eventId?: string }) {
|
|||
// `TimelineRenderingType.Thread` context.
|
||||
const threadDrawerOpen = useThreadDrawerOpen();
|
||||
|
||||
// Desktop web: centre the composer at ~3/4 width (user point 15). Native /
|
||||
// mobile / tablet keep the full-width card.
|
||||
const isDesktop = useScreenSizeContext() === ScreenSize.Desktop;
|
||||
|
||||
// 1:1 DM (bubble layout): fit the composer to the same centred ~960px band as
|
||||
// the bubble timeline + the AI-bot chat, instead of the 75% desktop clamp.
|
||||
const isOneOnOne = useIsOneOnOne();
|
||||
const channelsMode = useChannelsMode();
|
||||
const isBubble = isOneOnOne && !channelsMode;
|
||||
|
||||
useEffect(() => {
|
||||
const el = composerWrapRef.current;
|
||||
if (!el) {
|
||||
|
|
@ -183,17 +171,11 @@ export function RoomView({ eventId }: { eventId?: string }) {
|
|||
onFocusCapture={() => setComposerHidden(false)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
css.ChatComposer,
|
||||
isBubble ? css.ComposerBubbleBand : isDesktop && css.ComposerDesktopClamp
|
||||
)}
|
||||
style={
|
||||
// ComposerBubbleBand owns its own (responsive) horizontal padding;
|
||||
// the non-bubble path keeps the fixed horseshoe-void inline padding.
|
||||
isBubble
|
||||
? undefined
|
||||
: { padding: `0 ${toRem(VOJO_HORSESHOE_GAP_PX)} ${toRem(VOJO_HORSESHOE_GAP_PX)}` }
|
||||
}
|
||||
// Every room class fits its composer to the same centred ~960px band
|
||||
// as the timeline (BubbleTimelineBand) and the AI-bot chat, so on wide
|
||||
// web the input lines up with the message column. The band owns its own
|
||||
// responsive horizontal padding, so no inline padding here.
|
||||
className={classNames(css.ChatComposer, css.ComposerBubbleBand)}
|
||||
>
|
||||
{tombstoneEvent ? (
|
||||
<RoomTombstone
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue