From 6f45f94f196f4f2aefa68522d976a995ba3e4aeb Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Sat, 11 Jul 2026 21:03:18 +1000 Subject: [PATCH] refactor: adjust RoomView component for improved mobile composer behavior and styling --- .../src/app/components/editor/Editor.css.ts | 79 +++++++++++++++++++ overlay/src/app/features/room/RoomView.tsx | 22 +++++- .../features/room/RoomViewFollowing.css.ts | 41 ++++++++++ .../features/room/room-composer-mobile.css.ts | 63 +++++++++++++++ 4 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 overlay/src/app/components/editor/Editor.css.ts create mode 100644 overlay/src/app/features/room/RoomViewFollowing.css.ts create mode 100644 overlay/src/app/features/room/room-composer-mobile.css.ts diff --git a/overlay/src/app/components/editor/Editor.css.ts b/overlay/src/app/components/editor/Editor.css.ts new file mode 100644 index 0000000..062ce0e --- /dev/null +++ b/overlay/src/app/components/editor/Editor.css.ts @@ -0,0 +1,79 @@ +import { style } from '@vanilla-extract/css'; +import { color, config, DefaultReset, toRem } from 'folds'; + +export const Editor = style([ + DefaultReset, + { + backgroundColor: color.SurfaceVariant.Container, + color: color.SurfaceVariant.OnContainer, + boxShadow: `inset 0 0 0 ${config.borderWidth.B300} ${color.SurfaceVariant.ContainerLine}`, + borderRadius: config.radii.R400, + overflow: 'hidden', + selectors: { + // Keyboard-flush dock: only a top edge so the input can sit on the IME. + '[data-composer-flush="true"] &': { + borderRadius: 0, + boxShadow: `inset 0 ${config.borderWidth.B300} 0 0 ${color.SurfaceVariant.ContainerLine}`, + }, + }, + }, +]); + +export const EditorOptions = style([ + DefaultReset, + { + padding: config.space.S200, + }, +]); + +export const EditorTextareaScroll = style({}); + +export const EditorTextarea = style([ + DefaultReset, + { + flexGrow: 1, + height: '100%', + padding: `${toRem(13)} ${toRem(1)}`, + selectors: { + [`${EditorTextareaScroll}:first-child &`]: { + paddingLeft: toRem(13), + }, + [`${EditorTextareaScroll}:last-child &`]: { + paddingRight: toRem(13), + }, + '&:focus': { + outline: 'none', + }, + }, + }, +]); + +export const EditorPlaceholderContainer = style([ + DefaultReset, + { + opacity: config.opacity.Placeholder, + pointerEvents: 'none', + userSelect: 'none', + }, +]); + +export const EditorPlaceholderTextVisual = style([ + DefaultReset, + { + display: 'block', + paddingTop: toRem(13), + paddingLeft: toRem(1), + }, +]); + +export const EditorToolbarBase = style({ + padding: `0 ${config.borderWidth.B300}`, +}); + +export const EditorToolbar = style({ + padding: config.space.S100, +}); + +export const MarkdownBtnBox = style({ + paddingRight: config.space.S100, +}); diff --git a/overlay/src/app/features/room/RoomView.tsx b/overlay/src/app/features/room/RoomView.tsx index 652805f..2b891ae 100644 --- a/overlay/src/app/features/room/RoomView.tsx +++ b/overlay/src/app/features/room/RoomView.tsx @@ -14,7 +14,7 @@ import { RoomTimeline } from './RoomTimeline'; import { RoomViewTyping } from './RoomViewTyping'; import { RoomTombstone } from './RoomTombstone'; import { RoomInput } from './RoomInput'; -import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing'; +import { RoomViewFollowing } from './RoomViewFollowing'; import { Page } from '../../components/page'; import { RoomViewHeader } from './RoomViewHeader'; import { useKeyDown } from '../../hooks/useKeyDown'; @@ -27,6 +27,7 @@ import { activeThreadIdAtomFamily } from '../../state/activeThread'; import { ThreadView } from './ThreadView'; import { MobileSwipeToReplyLayer } from '../../components/mobile/MobileSwipeToReplyLayer'; import { useMobileKeyboardLayout } from '../../hooks/useMobileKeyboardLayout'; +import * as composerCss from './room-composer-mobile.css'; const FN_KEYS_REGEX = /^F\d+$/; const shouldFocusMessageField = (evt: KeyboardEvent): boolean => { @@ -103,7 +104,8 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) { ) ); - const composerPadding = isLandscape ? '0' : `0 ${config.space.S400}`; + const flushComposer = keyboardOpen || isLandscape; + const composerPadding = flushComposer ? '0' : `0 ${config.space.S400}`; const showHeader = !keyboardOpen; return ( @@ -125,7 +127,20 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) { - + + {!hideActivity && ( +
+
+ +
+
+ )}
{tombstoneEvent ? ( )}
- {hideActivity ? : }
)} diff --git a/overlay/src/app/features/room/RoomViewFollowing.css.ts b/overlay/src/app/features/room/RoomViewFollowing.css.ts new file mode 100644 index 0000000..58a326a --- /dev/null +++ b/overlay/src/app/features/room/RoomViewFollowing.css.ts @@ -0,0 +1,41 @@ +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; +import { DefaultReset, color, config } from 'folds'; + +export const RoomViewFollowingPlaceholder = style([ + DefaultReset, + { + display: 'none', + height: 0, + }, +]); + +export const RoomViewFollowing = recipe({ + base: [ + DefaultReset, + { + minHeight: 0, + padding: `${config.space.S100} ${config.space.S200}`, + width: 'auto', + maxWidth: '100%', + backgroundColor: 'transparent', + color: color.Surface.OnContainer, + outline: 'none', + }, + ], + variants: { + clickable: { + true: { + cursor: 'pointer', + selectors: { + '&:hover, &:focus-visible': { + color: color.Primary.Main, + }, + '&:active': { + color: color.Primary.Main, + }, + }, + }, + }, + }, +}); diff --git a/overlay/src/app/features/room/room-composer-mobile.css.ts b/overlay/src/app/features/room/room-composer-mobile.css.ts new file mode 100644 index 0000000..9f8beee --- /dev/null +++ b/overlay/src/app/features/room/room-composer-mobile.css.ts @@ -0,0 +1,63 @@ +import { style } from '@vanilla-extract/css'; +import { recipe } from '@vanilla-extract/recipes'; +import { DefaultReset, color, config } from 'folds'; + +export const ComposerDock = style({ + position: 'relative', +}); + +/** Read receipts float above the input so they don't reserve layout height. */ +export const FollowingFloat = style({ + position: 'absolute', + bottom: '100%', + left: 0, + right: 0, + zIndex: 2, + display: 'flex', + justifyContent: 'flex-end', + pointerEvents: 'none', + padding: `0 ${config.space.S200}`, +}); + +export const FollowingFloatHit = style({ + pointerEvents: 'auto', + maxWidth: '100%', +}); + +export const RoomViewFollowingPlaceholder = style([ + DefaultReset, + { + display: 'none', + height: 0, + }, +]); + +export const RoomViewFollowing = recipe({ + base: [ + DefaultReset, + { + minHeight: 0, + padding: `${config.space.S100} ${config.space.S200}`, + width: 'auto', + maxWidth: '100%', + backgroundColor: 'transparent', + color: color.Surface.OnContainer, + outline: 'none', + }, + ], + variants: { + clickable: { + true: { + cursor: 'pointer', + selectors: { + '&:hover, &:focus-visible': { + color: color.Primary.Main, + }, + '&:active': { + color: color.Primary.Main, + }, + }, + }, + }, + }, +});