fix: precise chat layout alignment to match Aether Drift prototype
Message rows: 20px horizontal padding, 3px vertical, 56px avatar column, subtle 2.5% hover. Channel headers: border instead of shadow, text # hash, 10px gap, 6px button radius. Date dividers: 8px margin, 11px text, border-hard lines. Input bubble: 34px buttons with 18px SVGs, 10px textarea padding. Inline code: lavender on #111115 with border-soft border.
This commit is contained in:
@@ -149,7 +149,7 @@ function buildComponents(): Components {
|
||||
// Inline code
|
||||
return (
|
||||
<code
|
||||
className="px-[0.35em] py-[0.15em] bg-surface-elevated rounded-[3px] text-[0.875em] font-mono text-accent-coral"
|
||||
className="px-[6px] py-px bg-[#111115] border border-border-soft rounded text-[13.5px] font-mono text-accent-lavender"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -146,23 +146,23 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className={`group relative flex px-4 py-0.5 transition-colors ${isFirstInGroup || message.replyTo ? 'mt-[1.0625rem]' : ''} ${
|
||||
className={`group relative flex px-5 py-[3px] transition-colors ${isFirstInGroup || message.replyTo ? 'mt-[1.0625rem]' : ''} ${
|
||||
isMentioned
|
||||
? 'bg-accent-amber/10 border-l-2 border-l-accent-amber hover:bg-accent-amber/15'
|
||||
: 'hover:bg-interactive-hover'
|
||||
: 'hover:bg-[rgba(255,255,255,0.025)]'
|
||||
}`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{/* Reply Line */}
|
||||
{message.replyTo && (
|
||||
<div className="absolute left-[36px] top-[-14px] w-[33px] h-[22px] border-l-2 border-t-2 border-interactive-muted rounded-tl-[6px] opacity-60" />
|
||||
<div className="absolute left-[40px] top-[-14px] w-[30px] h-[22px] border-l-2 border-t-2 border-interactive-muted rounded-tl-[6px] opacity-60" />
|
||||
)}
|
||||
|
||||
{/* Avatar or timestamp column */}
|
||||
<div className="w-[72px] flex-shrink-0 flex items-start justify-start pl-0.5">
|
||||
<div className="w-14 flex-shrink-0 flex items-start justify-start">
|
||||
{isFirstInGroup || message.replyTo ? (
|
||||
<div className="mt-1">
|
||||
<div className="mt-0.5">
|
||||
<Avatar
|
||||
src={message.user.avatar}
|
||||
name={displayName}
|
||||
@@ -172,14 +172,14 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className={`text-[11px] text-txt-tertiary opacity-0 group-hover:opacity-100 mt-2 select-none w-full text-center leading-[1.375rem] font-medium`}>
|
||||
<span className={`text-[11px] text-txt-tertiary opacity-0 group-hover:opacity-100 mt-[3px] select-none w-full text-center leading-[1.375rem]`}>
|
||||
{formatHoverTime(message.createdAt)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0 pr-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
{message.replyTo && (
|
||||
<div className="flex items-center gap-1 mb-1 ml-[-4px] opacity-80 hover:opacity-100 cursor-pointer group/reply">
|
||||
<Avatar src={message.replyTo.user.avatar} name={message.replyTo.user.username} size={16} user={message.replyTo.user} />
|
||||
@@ -204,7 +204,7 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
<span className="text-[11px] text-txt-tertiary leading-tight font-medium hover:cursor-default">
|
||||
<span className="text-[11px] text-txt-tertiary leading-tight hover:cursor-default">
|
||||
{formatTime(message.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -291,14 +291,14 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-start px-1">
|
||||
<div className="flex items-center pl-[10px] pr-1">
|
||||
{/* File attach button */}
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="p-3 text-txt-tertiary hover:text-txt-secondary transition-colors sticky top-0"
|
||||
className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] text-txt-tertiary hover:text-txt-secondary transition-colors flex-shrink-0"
|
||||
title="Attach file"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -324,7 +324,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`}
|
||||
className="flex-1 py-[11px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
|
||||
className="flex-1 py-[10px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
|
||||
rows={1}
|
||||
disabled={isUploading}
|
||||
/>
|
||||
@@ -340,22 +340,22 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
)}
|
||||
|
||||
{/* GIF button */}
|
||||
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="GIF">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<button className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] text-txt-tertiary hover:text-txt-secondary transition-colors flex-shrink-0" title="GIF">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2 5.5A2.5 2.5 0 0 1 4.5 3h15A2.5 2.5 0 0 1 22 5.5v13a2.5 2.5 0 0 1-2.5 2.5h-15A2.5 2.5 0 0 1 2 18.5v-13ZM5.1 14V10h3.2v1.2H6.5v.6h1.6v1.1H6.5V14H5.1Zm4.5 0V10h1.4v4H9.6Zm2.5 0V10h3.2v1.2h-1.8v.5h1.6v1h-1.6V14h-1.4Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Sticker button */}
|
||||
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="Stickers">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<button className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] text-txt-tertiary hover:text-txt-secondary transition-colors flex-shrink-0" title="Stickers">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12.5 2C6.81 2 2 6.81 2 12.5S6.81 23 12.5 23c1.31 0 2.56-.25 3.73-.7l5.07-5.07c.45-1.17.7-2.42.7-3.73C22 7.81 17.19 2 12.5 2Zm0 19c-4.69 0-8.5-3.81-8.5-8.5S7.81 4 12.5 4 21 7.81 21 12.5c0 .89-.14 1.74-.4 2.54l-3.56 3.56c-.8.26-1.65.4-2.54.4ZM8 11.5c.83 0 1.5-.67 1.5-1.5S8.83 8.5 8 8.5 6.5 9.17 6.5 10s.67 1.5 1.5 1.5Zm6 0c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5Zm-1 3.5c-2.33 0-4.31-1.46-5.11-3.5h10.22c-.8 2.04-2.78 3.5-5.11 3.5Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Emoji button */}
|
||||
<button className="p-2 text-txt-tertiary hover:text-txt-secondary transition-colors" title="Emoji">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<button className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] text-txt-tertiary hover:text-txt-secondary transition-colors flex-shrink-0" title="Emoji">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5s.67 1.5 1.5 1.5zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -124,7 +124,7 @@ export function MessageList({ channelId }: MessageListProps) {
|
||||
|
||||
{!hasMore && <WelcomeHeader channelId={channelId} />}
|
||||
|
||||
<div className="pb-6 md:pb-[88px]">
|
||||
<div className="pt-4 pb-6 md:pb-14">
|
||||
{messages.map((msg, i) => {
|
||||
const prevMsg = messages[i - 1];
|
||||
const showDate = shouldShowDateDivider(prevMsg, msg);
|
||||
@@ -133,12 +133,12 @@ export function MessageList({ channelId }: MessageListProps) {
|
||||
return (
|
||||
<React.Fragment key={msg.id}>
|
||||
{showDate && (
|
||||
<div className="flex items-center px-4 my-6 select-none pointer-events-none">
|
||||
<div className="flex-1 h-[1px] bg-interactive-muted" />
|
||||
<span className="px-2 text-[12px] font-bold text-txt-tertiary leading-tight">
|
||||
<div className="flex items-center px-5 my-2 select-none pointer-events-none">
|
||||
<div className="flex-1 h-[1px] bg-border-hard" />
|
||||
<span className="px-[14px] text-[11px] font-bold text-txt-tertiary leading-tight">
|
||||
{formatDateDivider(msg.createdAt)}
|
||||
</span>
|
||||
<div className="flex-1 h-[1px] bg-interactive-muted" />
|
||||
<div className="flex-1 h-[1px] bg-border-hard" />
|
||||
</div>
|
||||
)}
|
||||
<Message
|
||||
|
||||
Reference in New Issue
Block a user