feat: Phase 2 Aether Drift — chat component token migration + scroll fix
Migrate all 8 chat rendering components from Discord tokens to Aether Drift design system: Message, MarkdownRenderer, MentionBadge, MentionPopover, FriendsPage, TypingIndicator, Embed, ImagePreview. Eliminates ~83 discord-* class references and ~24 hardcoded hex values from components/chat/. Fix critical scroll regression from Phase 1 grid migration by adding explicit grid-rows-[minmax(0,1fr)] and min-h-0 to AppLayout's grid container, restoring the height constraint chain to MessageList's overflow-y-auto.
This commit is contained in:
@@ -74,13 +74,13 @@ export function FriendsPage() {
|
||||
case 'online':
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<h2 className="text-xs font-bold text-discord-text-muted uppercase mb-4 tracking-wider px-2">
|
||||
<h2 className="text-xs font-bold text-txt-tertiary uppercase mb-4 tracking-wider px-2">
|
||||
Online — {onlineFriends.length}
|
||||
</h2>
|
||||
{onlineFriends.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
||||
<img src="/friends-empty.svg" alt="" className="w-64 h-64 mb-4" onError={(e) => (e.target as any).style.display='none'} />
|
||||
<p className="text-discord-text-muted">No one's around to play with Wumpus.</p>
|
||||
<p className="text-txt-tertiary">No one's around to play with Wumpus.</p>
|
||||
</div>
|
||||
) : (
|
||||
onlineFriends.map(friend => (
|
||||
@@ -92,12 +92,12 @@ export function FriendsPage() {
|
||||
case 'all':
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<h2 className="text-xs font-bold text-discord-text-muted uppercase mb-4 tracking-wider px-2">
|
||||
<h2 className="text-xs font-bold text-txt-tertiary uppercase mb-4 tracking-wider px-2">
|
||||
All Friends — {friends.length}
|
||||
</h2>
|
||||
{friends.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
||||
<p className="text-discord-text-muted">Wumpus is waiting on friends. You can add them!</p>
|
||||
<p className="text-txt-tertiary">Wumpus is waiting on friends. You can add them!</p>
|
||||
</div>
|
||||
) : (
|
||||
friends.map(friend => (
|
||||
@@ -109,12 +109,12 @@ export function FriendsPage() {
|
||||
case 'pending':
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<h2 className="text-xs font-bold text-discord-text-muted uppercase mb-4 tracking-wider px-2">
|
||||
<h2 className="text-xs font-bold text-txt-tertiary uppercase mb-4 tracking-wider px-2">
|
||||
Pending — {pendingIncoming.length + pendingOutgoing.length}
|
||||
</h2>
|
||||
{[...pendingIncoming, ...pendingOutgoing].length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full opacity-60">
|
||||
<p className="text-discord-text-muted">There are no pending friend requests. Here's Wumpus for now!</p>
|
||||
<p className="text-txt-tertiary">There are no pending friend requests. Here's Wumpus for now!</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -142,26 +142,26 @@ export function FriendsPage() {
|
||||
case 'add':
|
||||
return (
|
||||
<div className="flex-1 p-8">
|
||||
<h2 className="text-base font-bold text-discord-text-primary uppercase mb-2">Add Friend</h2>
|
||||
<p className="text-sm text-discord-text-muted mb-4">You can add friends with their Backspace username.</p>
|
||||
<h2 className="text-base font-bold text-txt-primary uppercase mb-2">Add Friend</h2>
|
||||
<p className="text-sm text-txt-tertiary mb-4">You can add friends with their Backspace username.</p>
|
||||
<form onSubmit={handleAddFriend} className="relative mb-8">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="You can add a friend with their username"
|
||||
value={addUsername}
|
||||
onChange={(e) => setAddUsername(e.target.value)}
|
||||
className="w-full bg-discord-bg-tertiary text-discord-text-primary px-4 py-3 rounded-lg border border-transparent focus:border-discord-text-link outline-none transition-all placeholder:text-discord-text-muted/50"
|
||||
className="w-full bg-surface-base text-txt-primary px-4 py-3 rounded-lg border border-transparent focus:border-txt-link outline-none transition-all placeholder:text-txt-tertiary/50"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!addUsername.trim() || isLoading}
|
||||
className="absolute right-2 top-1.5 px-4 py-1.5 bg-discord-blurple hover:bg-discord-blurple-hover disabled:opacity-50 disabled:bg-discord-blurple text-white text-sm font-medium rounded transition-colors"
|
||||
className="absolute right-2 top-1.5 px-4 py-1.5 bg-accent-primary hover:bg-accent-primary-hover disabled:opacity-50 disabled:bg-accent-primary text-white text-sm font-medium rounded transition-colors"
|
||||
>
|
||||
Send Friend Request
|
||||
</button>
|
||||
</form>
|
||||
{addStatus && (
|
||||
<div className={`text-sm p-3 rounded-lg border ${addStatus.type === 'success' ? 'text-discord-text-positive border-discord-green/20 bg-discord-green/5' : 'text-discord-text-danger border-discord-red/20 bg-discord-red/5'}`}>
|
||||
<div className={`text-sm p-3 rounded-lg border ${addStatus.type === 'success' ? 'text-txt-positive border-status-online/20 bg-status-online/5' : 'text-txt-danger border-accent-rose/20 bg-accent-rose/5'}`}>
|
||||
{addStatus.message}
|
||||
</div>
|
||||
)}
|
||||
@@ -171,17 +171,17 @@ export function FriendsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-discord-bg-primary h-full">
|
||||
<div className="flex-1 flex flex-col bg-surface-chat h-full">
|
||||
{/* Header */}
|
||||
<div className="h-12 px-4 flex items-center shadow-header flex-shrink-0 z-10 bg-discord-bg-primary">
|
||||
<div className="h-12 px-4 flex items-center shadow-header flex-shrink-0 z-10 bg-surface-chat">
|
||||
<div className="flex items-center gap-2 mr-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-discord-text-muted">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
|
||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
|
||||
</svg>
|
||||
<span className="font-bold text-discord-text-primary">Friends</span>
|
||||
<span className="font-bold text-txt-primary">Friends</span>
|
||||
</div>
|
||||
|
||||
<div className="w-[1px] h-6 bg-discord-bg-accent mx-2" />
|
||||
<div className="w-[1px] h-6 bg-surface-elevated mx-2" />
|
||||
|
||||
<div className="flex items-center gap-4 ml-2">
|
||||
<TabButton active={activeTab === 'online'} onClick={() => setActiveTab('online')}>Online</TabButton>
|
||||
@@ -189,7 +189,7 @@ export function FriendsPage() {
|
||||
<TabButton active={activeTab === 'pending'} onClick={() => setActiveTab('pending')}>
|
||||
Pending
|
||||
{(pendingIncoming.length > 0) && (
|
||||
<span className="ml-2 px-1.5 py-0.5 bg-discord-red text-white text-[10px] rounded-full leading-none">
|
||||
<span className="ml-2 px-1.5 py-0.5 bg-accent-rose text-white text-[10px] rounded-full leading-none">
|
||||
{pendingIncoming.length}
|
||||
</span>
|
||||
)}
|
||||
@@ -197,7 +197,7 @@ export function FriendsPage() {
|
||||
<button
|
||||
onClick={() => setActiveTab('add')}
|
||||
className={`px-2 py-0.5 rounded text-[14px] font-medium transition-all ${
|
||||
activeTab === 'add' ? 'text-discord-green bg-transparent' : 'bg-discord-green text-white hover:bg-discord-green/90'
|
||||
activeTab === 'add' ? 'text-status-online bg-transparent' : 'bg-status-online text-white hover:bg-status-online/90'
|
||||
}`}
|
||||
>
|
||||
Add Friend
|
||||
@@ -219,7 +219,7 @@ function TabButton({ children, active, onClick }: { children: React.ReactNode, a
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-2 py-0.5 rounded-[4px] text-[16px] font-medium transition-colors ${
|
||||
active ? 'bg-discord-modifier-selected text-white' : 'text-discord-text-muted hover:bg-discord-modifier-hover hover:text-discord-text-secondary'
|
||||
active ? 'bg-interactive-selected text-white' : 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
@@ -229,21 +229,21 @@ function TabButton({ children, active, onClick }: { children: React.ReactNode, a
|
||||
|
||||
function FriendItem({ friend, onRemove, onDm }: { friend: Friend, onRemove: () => void, onDm: () => void }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-3 h-[62px] rounded-[8px] hover:bg-discord-modifier-hover group transition-colors border-t border-discord-modifier-accent mx-2">
|
||||
<div className="flex items-center justify-between px-3 h-[62px] rounded-[8px] hover:bg-interactive-hover group transition-colors border-t border-interactive-muted mx-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar src={friend.avatar} name={friend.displayName ?? friend.username} size={32} status={friend.status} />
|
||||
<div className="flex flex-col leading-tight">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-discord-text-primary font-semibold text-[15px]">{friend.displayName ?? friend.username}</span>
|
||||
<span className="text-discord-text-muted text-[13px] opacity-0 group-hover:opacity-100 transition-opacity font-medium">@{friend.username}</span>
|
||||
<span className="text-txt-primary font-semibold text-[15px]">{friend.displayName ?? friend.username}</span>
|
||||
<span className="text-txt-tertiary text-[13px] opacity-0 group-hover:opacity-100 transition-opacity font-medium">@{friend.username}</span>
|
||||
</div>
|
||||
<span className="text-[12px] text-discord-text-muted font-medium uppercase">{friend.status}</span>
|
||||
<span className="text-[12px] text-txt-tertiary font-medium uppercase">{friend.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity pr-2">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onDm(); }}
|
||||
className="w-9 h-9 flex items-center justify-center bg-discord-bg-tertiary rounded-full text-discord-text-muted hover:text-discord-text-primary transition-colors"
|
||||
className="w-9 h-9 flex items-center justify-center bg-surface-base rounded-full text-txt-tertiary hover:text-txt-primary transition-colors"
|
||||
title="Message"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -252,7 +252,7 @@ function FriendItem({ friend, onRemove, onDm }: { friend: Friend, onRemove: () =
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onRemove(); }}
|
||||
className="w-9 h-9 flex items-center justify-center bg-discord-bg-tertiary rounded-full text-discord-text-muted hover:text-discord-red transition-colors"
|
||||
className="w-9 h-9 flex items-center justify-center bg-surface-base rounded-full text-txt-tertiary hover:text-txt-danger transition-colors"
|
||||
title="Remove Friend"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -275,15 +275,15 @@ function RequestItem({ request, type, onAccept, onDecline, onCancel }: {
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-discord-modifier-hover group transition-colors border-t border-discord-modifier-accent mx-2">
|
||||
<div className="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-interactive-hover group transition-colors border-t border-interactive-muted mx-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={32} status={user.status as any} />
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-discord-text-primary font-bold text-sm">{user.displayName ?? user.username}</span>
|
||||
<span className="text-discord-text-muted text-xs">@{user.username}</span>
|
||||
<span className="text-txt-primary font-bold text-sm">{user.displayName ?? user.username}</span>
|
||||
<span className="text-txt-tertiary text-xs">@{user.username}</span>
|
||||
</div>
|
||||
<span className="text-xs text-discord-text-muted">{type === 'incoming' ? 'Incoming Friend Request' : 'Outgoing Friend Request'}</span>
|
||||
<span className="text-xs text-txt-tertiary">{type === 'incoming' ? 'Incoming Friend Request' : 'Outgoing Friend Request'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -291,7 +291,7 @@ function RequestItem({ request, type, onAccept, onDecline, onCancel }: {
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAccept?.()}
|
||||
className="p-2 bg-discord-bg-tertiary rounded-full text-discord-green hover:bg-discord-green hover:text-white transition-all"
|
||||
className="p-2 bg-surface-base rounded-full text-status-online hover:bg-status-online hover:text-white transition-all"
|
||||
title="Accept"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -300,7 +300,7 @@ function RequestItem({ request, type, onAccept, onDecline, onCancel }: {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDecline?.()}
|
||||
className="p-2 bg-discord-bg-tertiary rounded-full text-discord-red hover:bg-discord-red hover:text-white transition-all"
|
||||
className="p-2 bg-surface-base rounded-full text-txt-danger hover:bg-accent-rose hover:text-white transition-all"
|
||||
title="Decline"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -311,7 +311,7 @@ function RequestItem({ request, type, onAccept, onDecline, onCancel }: {
|
||||
) : (
|
||||
<button
|
||||
onClick={() => onCancel?.()}
|
||||
className="p-2 bg-discord-bg-tertiary rounded-full text-discord-text-muted hover:text-discord-red transition-all"
|
||||
className="p-2 bg-surface-base rounded-full text-txt-tertiary hover:text-txt-danger transition-all"
|
||||
title="Cancel Request"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
||||
Reference in New Issue
Block a user