feat(chat): render Media/Files + Group Info drawers inline, not overlaid
Both right-hand panels used absolute inset-y-0 right-0 and floated on top of the conversation, hiding the messages directly underneath the panel and looking unlike Discord's actual layout. Restructure: * MediaFilesDrawer: drop absolute/z-index/shadow chrome, become a static flex column (w-[380px] shrink-0) with a left border. Internal layout unchanged. * GroupInfoPanel: same treatment (w-[320px] shrink-0). Dropped the backdrop-blur and slide-up animation that only made sense as a modal. * ConversationPage: wrap the chat content (voice rail, in-call panel, messages list, drag-overlay, input form) in a new `flex min-w-0 flex-1 flex-col` chat-column, and make that column a sibling of the drawers inside a new `flex flex-1 flex-row` row. The conversation header + search bar stay full-width above the row. Result: opening a drawer narrows the chat column instead of covering it, matching Discord's behaviour. The chat-column wrapper also carries the `relative` anchor previously held by the outer wrapper so the drag-and-drop overlay positions correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,7 +93,7 @@ export function GroupInfoPanel({ open, onClose, conversation }: Props) {
|
|||||||
<aside
|
<aside
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label={t('app:group.info_title')}
|
aria-label={t('app:group.info_title')}
|
||||||
className="absolute inset-y-0 right-0 z-20 flex w-[320px] flex-col border-l border-white/5 bg-ink-900/95 shadow-xl backdrop-blur-xl animate-slide-up"
|
className="flex w-[320px] shrink-0 flex-col border-l border-white/5 bg-ink-900/95"
|
||||||
>
|
>
|
||||||
<header className="flex items-center justify-between border-b border-white/5 px-5 py-4">
|
<header className="flex items-center justify-between border-b border-white/5 px-5 py-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function MediaFilesDrawer({ open, index, senderNameFor, onJumpToMessage,
|
|||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="absolute inset-y-0 right-0 z-40 flex w-full max-w-[380px] flex-col border-l border-line bg-surface-2 shadow-2xl dark:bg-[#2b2d31]">
|
<aside className="flex w-[380px] shrink-0 flex-col border-l border-line bg-surface-2 dark:bg-[#2b2d31]">
|
||||||
<header className="flex min-h-[65px] items-center gap-3 border-b border-line px-4">
|
<header className="flex min-h-[65px] items-center gap-3 border-b border-line px-4">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-accent/10 text-accent">
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-accent/10 text-accent">
|
||||||
<ImageIcon className="h-5 w-5" />
|
<ImageIcon className="h-5 w-5" />
|
||||||
|
|||||||
@@ -643,31 +643,19 @@ export function ConversationPage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isGroup && conversation && (
|
{/* Layout row: chat column on the left grows to fill remaining width;
|
||||||
<GroupInfoPanel
|
right-hand drawers (Media/Files, Group Info) render as inline
|
||||||
open={infoPanelOpen}
|
siblings so opening one narrows the chat instead of floating on
|
||||||
onClose={() => setInfoPanelOpen(false)}
|
top of it (Discord parity). The chat-column wrapper holds the
|
||||||
conversation={conversation}
|
`relative` anchor for the drag-and-drop overlay further below. */}
|
||||||
/>
|
<div className="flex min-h-0 flex-1 flex-row">
|
||||||
)}
|
<div className="relative flex min-w-0 flex-1 flex-col">
|
||||||
|
{/* Discord-style persistent voice-channel rail. Always visible in groups
|
||||||
<MediaFilesDrawer
|
so anyone can pop in without an invite-ring; hidden in 1:1s unless
|
||||||
open={mediaDrawerOpen}
|
someone is already waiting. Hides automatically once we're in. */}
|
||||||
index={attachmentIndex}
|
{conversation && !incomingHere && <VoiceChannelRail conversation={conversation} />}
|
||||||
senderNameFor={senderNameFor}
|
{incomingHere && conversation && <IncomingCallPanel conversation={conversation} />}
|
||||||
onJumpToMessage={(messageId) => {
|
{conversation && <InCallPanel conversation={conversation} />}
|
||||||
setMediaDrawerOpen(false);
|
|
||||||
jumpToMessage(messageId);
|
|
||||||
}}
|
|
||||||
onClose={() => setMediaDrawerOpen(false)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Discord-style persistent voice-channel rail. Always visible in groups
|
|
||||||
so anyone can pop in without an invite-ring; hidden in 1:1s unless
|
|
||||||
someone is already waiting. Hides automatically once we're in. */}
|
|
||||||
{conversation && !incomingHere && <VoiceChannelRail conversation={conversation} />}
|
|
||||||
{incomingHere && conversation && <IncomingCallPanel conversation={conversation} />}
|
|
||||||
{conversation && <InCallPanel conversation={conversation} />}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
@@ -1019,6 +1007,27 @@ export function ConversationPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isGroup && conversation && (
|
||||||
|
<GroupInfoPanel
|
||||||
|
open={infoPanelOpen}
|
||||||
|
onClose={() => setInfoPanelOpen(false)}
|
||||||
|
conversation={conversation}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<MediaFilesDrawer
|
||||||
|
open={mediaDrawerOpen}
|
||||||
|
index={attachmentIndex}
|
||||||
|
senderNameFor={senderNameFor}
|
||||||
|
onJumpToMessage={(messageId) => {
|
||||||
|
setMediaDrawerOpen(false);
|
||||||
|
jumpToMessage(messageId);
|
||||||
|
}}
|
||||||
|
onClose={() => setMediaDrawerOpen(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ForwardDialog
|
<ForwardDialog
|
||||||
open={forwardTarget !== null}
|
open={forwardTarget !== null}
|
||||||
|
|||||||
Reference in New Issue
Block a user