fix(mobile): deep-audit structural fixes — dialogs, toasts, call bar (N1)
From the 6-agent deep per-feature audit. Mobile-gated / consistency fixes; desktop unchanged except two intentional dialog-width normalizations noted below. - In-call control bar: wrap="Wrap" on the SequenceCard so the compact two-group row wraps on the narrowest phones (<=390px) instead of pushing End off-screen (M1 fixed the 500-750px band; this covers narrower). Desktop stays one row. - In-call soundboard popout: clamp maxWidth to the viewport (like M5's screenshare popover) so it can't overflow a narrow phone. - Report-Message dialog + "Seen by" (EventReaders) modals (Message.tsx x2 + RoomViewFollowing): add useModalStyle so they go full-screen on mobile like their sibling report/receipt modals (they floated as fixed cards before). - In-app toast container: full-width toasts inset from both edges on mobile (ScreenSize.Mobile); a fixed 280-340px card previously overflowed a narrow phone. Desktop byte-identical (bottom-right floating card). - Policy-list tabs + audio-controls rows: wrap="Wrap" (inert on desktop). Intentional desktop deltas (normalizing to existing sibling modals, verified by two review passes as consistent, not regressions): Report dialog max-width 380->480px; EventReaders modals 460->360px. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ export const MediaControl = as<'div', MediaControlProps>(
|
|||||||
({ before, after, leftControl, rightControl, children, ...props }, ref) => (
|
({ before, after, leftControl, rightControl, children, ...props }, ref) => (
|
||||||
<Box grow="Yes" direction="Column" gap="300" {...props} ref={ref}>
|
<Box grow="Yes" direction="Column" gap="300" {...props} ref={ref}>
|
||||||
{before && <Box direction="Column">{before}</Box>}
|
{before && <Box direction="Column">{before}</Box>}
|
||||||
<Box alignItems="Center" gap="200">
|
<Box alignItems="Center" gap="200" wrap="Wrap">
|
||||||
<Box alignItems="Center" grow="Yes" gap="Inherit">
|
<Box alignItems="Center" grow="Yes" gap="Inherit">
|
||||||
{leftControl}
|
{leftControl}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
radii="500"
|
radii="500"
|
||||||
alignItems="Center"
|
alignItems="Center"
|
||||||
justifyContent="SpaceBetween"
|
justifyContent="SpaceBetween"
|
||||||
|
wrap="Wrap"
|
||||||
>
|
>
|
||||||
<Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
|
<Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
|
||||||
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
|
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
|
||||||
|
|||||||
@@ -135,7 +135,12 @@ export function CallSoundboard({ callEmbed }: CallSoundboardProps) {
|
|||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Menu style={{ maxWidth: manage ? toRem(420) : toRem(340), maxHeight: '70vh' }}>
|
<Menu
|
||||||
|
style={{
|
||||||
|
maxWidth: `min(${manage ? toRem(420) : toRem(340)}, calc(100vw - 2 * ${config.space.S400}))`,
|
||||||
|
maxHeight: '70vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box direction="Column" style={{ maxHeight: '70vh' }}>
|
<Box direction="Column" style={{ maxHeight: '70vh' }}>
|
||||||
<Box
|
<Box
|
||||||
shrink="No"
|
shrink="No"
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export function PolicyListViewer({ requestClose }: PolicyListViewerProps) {
|
|||||||
gap="300"
|
gap="300"
|
||||||
>
|
>
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Box gap="200">
|
<Box gap="200" wrap="Wrap">
|
||||||
<TabButton
|
<TabButton
|
||||||
label="Users"
|
label="Users"
|
||||||
count={userEntries.length}
|
count={userEntries.length}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|||||||
import { useRoomLatestRenderedEvent } from '../../hooks/useRoomLatestRenderedEvent';
|
import { useRoomLatestRenderedEvent } from '../../hooks/useRoomLatestRenderedEvent';
|
||||||
import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
|
import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
|
||||||
import { EventReaders } from '../../components/event-readers';
|
import { EventReaders } from '../../components/event-readers';
|
||||||
|
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
|
|
||||||
export function RoomViewFollowingPlaceholder() {
|
export function RoomViewFollowingPlaceholder() {
|
||||||
@@ -34,6 +35,7 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>(
|
|||||||
({ className, room, ...props }, ref) => {
|
({ className, room, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const modalStyle = useModalStyle(360);
|
||||||
const latestEvent = useRoomLatestRenderedEvent(room);
|
const latestEvent = useRoomLatestRenderedEvent(room);
|
||||||
const latestEventReaders = useRoomEventReaders(room, latestEvent?.getId());
|
const latestEventReaders = useRoomEventReaders(room, latestEvent?.getId());
|
||||||
const names = latestEventReaders
|
const names = latestEventReaders
|
||||||
@@ -55,7 +57,7 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>(
|
|||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Modal variant="Surface" size="300">
|
<Modal variant="Surface" size="300" style={modalStyle}>
|
||||||
<EventReaders room={room} eventId={eventId} requestClose={() => setOpen(false)} />
|
<EventReaders room={room} eventId={eventId} requestClose={() => setOpen(false)} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import { MessageLayout, MessageSpacing } from '../../../state/settings';
|
|||||||
import { msgTranslationActiveAtomFamily } from '../../../state/translation';
|
import { msgTranslationActiveAtomFamily } from '../../../state/translation';
|
||||||
import { chromeTranslationEngine } from '../../../utils/translation/chromeEngine';
|
import { chromeTranslationEngine } from '../../../utils/translation/chromeEngine';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
import { useModalStyle } from '../../../hooks/useModalStyle';
|
||||||
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { MsgAppearClass, SendingSpinClass } from '../../../styles/Animations.css';
|
import { MsgAppearClass, SendingSpinClass } from '../../../styles/Animations.css';
|
||||||
@@ -260,6 +261,7 @@ export const MessageReadReceiptItem = as<
|
|||||||
}
|
}
|
||||||
>(({ room, eventId, onClose, ...props }, ref) => {
|
>(({ room, eventId, onClose, ...props }, ref) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const modalStyle = useModalStyle(360);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -278,7 +280,7 @@ export const MessageReadReceiptItem = as<
|
|||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Modal variant="Surface" size="300">
|
<Modal variant="Surface" size="300" style={modalStyle}>
|
||||||
<EventReaders room={room} eventId={eventId} requestClose={handleClose} />
|
<EventReaders room={room} eventId={eventId} requestClose={handleClose} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
@@ -674,6 +676,7 @@ export const MessageReportItem = as<
|
|||||||
>(({ room, mEvent, onClose, ...props }, ref) => {
|
>(({ room, mEvent, onClose, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const modalStyle = useModalStyle(480);
|
||||||
const [reportState, reportMessage] = useAsyncCallback(
|
const [reportState, reportMessage] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(
|
||||||
(eventId: string, score: number, reason: string) =>
|
(eventId: string, score: number, reason: string) =>
|
||||||
@@ -715,7 +718,7 @@ export const MessageReportItem = as<
|
|||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog variant="Surface">
|
<Dialog variant="Surface" style={modalStyle}>
|
||||||
<Header
|
<Header
|
||||||
style={{
|
style={{
|
||||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useRef, CSSProperties } from 'react';
|
import React, { useEffect, useRef, CSSProperties } from 'react';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { color, config, Icon, IconButton, Icons } from 'folds';
|
import { color, config, Icon, IconButton, Icons } from 'folds';
|
||||||
|
import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize';
|
||||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
@@ -37,6 +38,7 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
// folds tokens so toasts render correctly on stock Cinny themes (the --lt-*
|
// folds tokens so toasts render correctly on stock Cinny themes (the --lt-*
|
||||||
// vars only exist while Terminal mode is active).
|
// vars only exist while Terminal mode is active).
|
||||||
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||||
|
const isMobile = useScreenSize() === ScreenSize.Mobile;
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -80,8 +82,11 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
}`,
|
}`,
|
||||||
borderRadius: config.radii.R400,
|
borderRadius: config.radii.R400,
|
||||||
padding: `${config.space.S300} ${config.space.S400}`,
|
padding: `${config.space.S300} ${config.space.S400}`,
|
||||||
minWidth: '280px',
|
// Full-width on phones (the container spans the viewport there); a fixed
|
||||||
maxWidth: '340px',
|
// 280-340px card would otherwise overflow a narrow screen.
|
||||||
|
minWidth: isMobile ? 0 : '280px',
|
||||||
|
maxWidth: isMobile ? 'none' : '340px',
|
||||||
|
width: isMobile ? '100%' : undefined,
|
||||||
boxShadow: lotusTerminal
|
boxShadow: lotusTerminal
|
||||||
? toast.sticky
|
? toast.sticky
|
||||||
? 'var(--lt-box-glow-cyan)'
|
? 'var(--lt-box-glow-cyan)'
|
||||||
@@ -216,13 +221,17 @@ export function LotusToastContainer() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toasts = useAtomValue(toastQueueAtom);
|
const toasts = useAtomValue(toastQueueAtom);
|
||||||
|
const isMobile = useScreenSize() === ScreenSize.Mobile;
|
||||||
|
|
||||||
if (toasts.length === 0) return null;
|
if (toasts.length === 0) return null;
|
||||||
|
|
||||||
const containerStyle: CSSProperties = {
|
const containerStyle: CSSProperties = {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: '1.5rem',
|
// Span the width just inside the screen edges on a phone (so full-width
|
||||||
right: '1.5rem',
|
// cards fit); float bottom-right on desktop.
|
||||||
|
bottom: isMobile ? config.space.S200 : '1.5rem',
|
||||||
|
right: isMobile ? config.space.S200 : '1.5rem',
|
||||||
|
left: isMobile ? config.space.S200 : undefined,
|
||||||
zIndex: zIndices.toast,
|
zIndex: zIndices.toast,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|||||||
Reference in New Issue
Block a user