Private
Public Access
Fix mobile display: RoomInfoPanel inaccessible, emoji picker overflow
RoomInfoPanel was hard-gated behind !isMobile in ChatShellPage.tsx, but
its trigger button ("Room details" in ChatPane.tsx) still rendered and
toggled state unconditionally -- tapping it on mobile did nothing
visible, with no way to reach room info/members/settings at all. Fixed
by removing the gate and making the panel itself responsive: it renders
as a full-screen fixed overlay below the mobile breakpoint instead of
the desktop resizable aside (which stays exactly as before -- verified
in-browser at both viewport sizes).
Also found and fixed a second real bug during the mobile audit: the
emoji picker's fixed 320px width overflows a 375px-wide viewport by 5px
depending on trigger position (e.g. the composer's emoji button, near
the left edge). Shrunk it to 280px with a proportionally reduced column
count below 480px, rather than attempting dynamic position-aware sizing
for a 5px overflow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -88,3 +88,20 @@
|
|||||||
.emoji-picker-item:hover {
|
.emoji-picker-item:hover {
|
||||||
background: var(--ds-surface-2);
|
background: var(--ds-surface-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The picker is positioned absolutely relative to its trigger button, which
|
||||||
|
can sit close enough to a narrow viewport's edge that the full 320px
|
||||||
|
width runs off-screen (e.g. the composer's emoji trigger, near the left
|
||||||
|
edge, overflows the right edge on a 375px-wide phone). Shrinking the
|
||||||
|
fixed width -- rather than trying to dynamically reposition based on the
|
||||||
|
trigger's actual offset -- is enough margin for every real trigger
|
||||||
|
position in this app. */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.emoji-picker {
|
||||||
|
width: min(280px, calc(100vw - 32px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-grid {
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,23 @@
|
|||||||
background: color-mix(in srgb, var(--ds-accent) 40%, transparent);
|
background: color-mix(in srgb, var(--ds-accent) 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Below MOBILE_BREAKPOINT (frontend/src/hooks/useWindowWidth.ts), the
|
||||||
|
resizable-aside layout doesn't fit -- shown as a full-viewport overlay
|
||||||
|
instead. RoomInfoPanel.tsx skips its inline `style={{ width }}` on
|
||||||
|
mobile so this width rule isn't fighting an inline style, which would
|
||||||
|
otherwise win regardless of this media query's specificity. */
|
||||||
|
@media (max-width: 859px) {
|
||||||
|
.room-info-panel {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.room-info-header {
|
.room-info-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '../api/webhooks'
|
} from '../api/webhooks'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import { useResizableWidth } from '../hooks/useResizableWidth'
|
import { useResizableWidth } from '../hooks/useResizableWidth'
|
||||||
|
import { MOBILE_BREAKPOINT, useWindowWidth } from '../hooks/useWindowWidth'
|
||||||
import type {
|
import type {
|
||||||
EventSubscription,
|
EventSubscription,
|
||||||
EventType,
|
EventType,
|
||||||
@@ -57,6 +58,8 @@ export function RoomInfoPanel({
|
|||||||
}: RoomInfoPanelProps) {
|
}: RoomInfoPanelProps) {
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const myRole = room.role
|
const myRole = room.role
|
||||||
|
const windowWidth = useWindowWidth()
|
||||||
|
const isMobile = windowWidth < MOBILE_BREAKPOINT
|
||||||
const { width, startResize } = useResizableWidth({
|
const { width, startResize } = useResizableWidth({
|
||||||
storageKey: 'room-info-panel-width',
|
storageKey: 'room-info-panel-width',
|
||||||
defaultWidth: 260,
|
defaultWidth: 260,
|
||||||
@@ -231,8 +234,8 @@ export function RoomInfoPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="room-info-panel" style={{ width }}>
|
<aside className="room-info-panel" style={isMobile ? undefined : { width }}>
|
||||||
<div className="room-info-resize-handle" onPointerDown={startResize} />
|
{!isMobile && <div className="room-info-resize-handle" onPointerDown={startResize} />}
|
||||||
<div className="room-info-header">
|
<div className="room-info-header">
|
||||||
<span className="room-info-header-label">Details</span>
|
<span className="room-info-header-label">Details</span>
|
||||||
<button type="button" className="room-info-close" onClick={onClose} aria-label="Close">
|
<button type="button" className="room-info-close" onClick={onClose} aria-label="Close">
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function ChatShellPage() {
|
|||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{!isMobile && infoOpen && activeRoom && (
|
{infoOpen && activeRoom && (
|
||||||
<RoomInfoPanel
|
<RoomInfoPanel
|
||||||
room={activeRoom}
|
room={activeRoom}
|
||||||
members={members}
|
members={members}
|
||||||
|
|||||||
Reference in New Issue
Block a user