From dcd4ef0011eda615de3948bf96bf3a9fcb51ebf2 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 2 Mar 2026 03:21:12 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Aether=20Drift=20UI=20polish=20?= =?UTF-8?q?=E2=80=94=20popout=20redesign,=20sidebar=20cleanup,=20status=20?= =?UTF-8?q?dot=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redesign UserProfilePopout with glass material, viewport clamping, flow-based avatar layout, and subtle send-message button - Server sidebar: downsize icons to 40px (matching prototype), remove nonfunctional Explore button, add separator before action buttons, replace Tooltip with native title, remove hover boxShadow artifacts - Avatar status dot: fixed 12px size with semi-transparent border for universal cutout effect matching the design prototype - MemberListToggleButton: distinct three-person icon (was duplicate of Add Friends) - FriendsPage: fix Add Friend button contrast (dark text on mint background) - ChannelSidebar: rename Nitro/Shop to Coming Soon placeholders --- .../web/src/components/chat/FriendsPage.tsx | 2 +- .../src/components/layout/ChannelSidebar.tsx | 21 ++-- .../layout/MemberListToggleButton.tsx | 2 +- .../src/components/layout/ServerSidebar.tsx | 102 ++++++++---------- packages/web/src/components/ui/Avatar.tsx | 11 +- .../src/components/ui/UserProfilePopout.tsx | 97 ++++++++++------- 6 files changed, 122 insertions(+), 113 deletions(-) diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx index 8e5be27e..5c022071 100644 --- a/packages/web/src/components/chat/FriendsPage.tsx +++ b/packages/web/src/components/chat/FriendsPage.tsx @@ -197,7 +197,7 @@ export function FriendsPage() { ); diff --git a/packages/web/src/components/layout/ServerSidebar.tsx b/packages/web/src/components/layout/ServerSidebar.tsx index 1a8ad02d..e51bec14 100644 --- a/packages/web/src/components/layout/ServerSidebar.tsx +++ b/packages/web/src/components/layout/ServerSidebar.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useServerStore } from '../../stores/serverStore'; import { useChatStore } from '../../stores/chatStore'; import { useUIStore } from '../../stores/uiStore'; -import { Tooltip } from '../ui/Tooltip'; + import { getServerGradient, HOME_GRADIENT } from '../../utils/gradients'; interface SidebarItemProps { @@ -13,7 +13,7 @@ interface SidebarItemProps { active: boolean; onClick: () => void; type?: 'server' | 'dm' | 'action'; - actionType?: 'add' | 'join' | 'explore'; + actionType?: 'add' | 'join'; hasUnread?: boolean; } @@ -22,94 +22,86 @@ function SidebarItem({ id, name, icon, active, onClick, type = 'server', actionT const firstLetter = name.charAt(0).toUpperCase(); const getPillHeight = () => { - if (active) return 'h-10'; - if (isHovered) return 'h-5'; + if (active) return 'h-8'; + if (isHovered) return 'h-4'; if (hasUnread && !active) return 'h-2'; return 'h-2 scale-0'; }; const backgroundStyle = useMemo((): React.CSSProperties | undefined => { - if (type === 'action') return undefined; + if (type === 'action') { + return { + background: isHovered ? 'rgba(134, 239, 172, 0.12)' : 'rgba(255, 255, 255, 0.04)', + }; + } if (type === 'dm') { - return { - background: HOME_GRADIENT.gradient, - ...(isHovered ? { boxShadow: `0 0 12px ${HOME_GRADIENT.glow}40` } : {}), - }; + return { background: HOME_GRADIENT.gradient }; } // Server type — if it has a custom icon image, no gradient needed if (icon) return undefined; const serverGrad = getServerGradient(id, name); - return { - background: serverGrad.gradient, - ...(isHovered ? { boxShadow: `0 0 12px ${serverGrad.glow}40` } : {}), - }; - }, [type, id, name, icon, active, isHovered]); + return { background: serverGrad.gradient }; + }, [type, id, name, icon, isHovered]); const getButtonClasses = () => { - const base = 'w-12 h-12 flex items-center justify-center transition-all duration-200 overflow-hidden relative group'; + const base = 'w-10 h-10 flex items-center justify-center duration-200 overflow-hidden [transition:border-radius_0.2s,background_0.2s,color_0.2s]'; if (type === 'dm') { - return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } if (type === 'action') { - return `${base} bg-surface-chat rounded-[24px] hover:rounded-[16px] text-status-online hover:bg-status-online hover:text-white`; + return `${base} rounded-[20px] hover:rounded-[13px] text-accent-mint`; } if (icon) { - return `${base} ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } - return `${base} text-white ${active ? 'rounded-[16px]' : 'rounded-[24px] hover:rounded-[16px]'}`; + return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; }; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Pill Indicator */} {(type === 'server' || type === 'dm') && ( -
+
)} - - - + + + + ) + ) : icon ? ( + {name} + ) : ( + {firstLetter} + )} +
); } @@ -167,7 +159,7 @@ export function ServerSidebar() { hasUnread={hasDmUnread} /> -
+
{servers.map((server) => ( ))} +
+ - {}} - type="action" - actionType="explore" - /> ); } diff --git a/packages/web/src/components/ui/Avatar.tsx b/packages/web/src/components/ui/Avatar.tsx index f970419a..a28b0eec 100644 --- a/packages/web/src/components/ui/Avatar.tsx +++ b/packages/web/src/components/ui/Avatar.tsx @@ -69,12 +69,13 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
{status && (
)} diff --git a/packages/web/src/components/ui/UserProfilePopout.tsx b/packages/web/src/components/ui/UserProfilePopout.tsx index 5c884a6e..b1cc14b9 100644 --- a/packages/web/src/components/ui/UserProfilePopout.tsx +++ b/packages/web/src/components/ui/UserProfilePopout.tsx @@ -18,6 +18,13 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout const addDmChannel = useServerStore((s) => s.addDmChannel); const displayName = user.displayName ?? user.username; + const top = position + ? Math.min(Math.max(8, position.top), window.innerHeight - 360) + : undefined; + const left = position + ? Math.min(Math.max(8, position.left), window.innerWidth - 316) + : undefined; + const handleSendMessage = async () => { try { const channel = await api.dm.create({ userId: user.id }); @@ -29,59 +36,77 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout console.error('Failed to create DM channel:', err); } }; - + return ( -
{/* Banner */} -
- - {/* Avatar Container */} -
-
+
+ + {/* Body */} +
+ {/* Avatar — negative margin pulls it into the banner while staying in flow */} +
- - {/* Content */} -
-
+ + {/* Name & info — flows naturally after avatar */} +
+
{displayName}
-
+
@{user.username}
- -
- -
-
Backspace Member Since
-
- {new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} -
-
- {user.customStatus && ( -
-
Status
-
{user.customStatus}
+
+ {user.customStatus}
)}
-
- - {/* Footer / Actions */} -
-