feat(user-menu): make relays and blossom servers collapsible dropdowns (#177)

* feat(user-menu): make relays and blossom servers collapsible dropdowns

Convert the relays and blossom servers sections in the user menu from
always-expanded lists to collapsible dropdown submenus. Each section
now shows a count next to the title and expands on click to show the
full list. This saves vertical space in the menu when users have many
relays or servers configured.

* refactor(user-menu): reorganize menu items for better UX

Reorder menu sections by logical grouping:
- Identity (user profile) at top
- Account config (wallet, relays, blossom) grouped together
- App preferences (theme) in consistent location
- Promotional (support grimoire) separated
- Session actions (login/logout) at bottom

This provides a more intuitive flow and consistent placement
regardless of login state.

* refactor(user-menu): revert dropdowns, add login/logout icons

- Revert relays and blossom servers back to flat lists (not dropdowns)
- Move login to first option for logged out users
- Add LogIn/LogOut icons from lucide-react
- Keep the reorganized menu structure

* style(user-menu): clean up icons and font consistency

- Make theme icon muted like other icons
- Remove HardDrive icons from blossom servers section
- Make blossom label consistent with relays (plain text)
- Remove unused HardDrive import

* fix(user-menu): close menu when clicking relays, blossom, or donate

Wrap RelayLink items in DropdownMenuItem with asChild to properly
trigger menu close on click. Convert Support Grimoire section from
a raw div to DropdownMenuItem so it also closes the menu.

* fix(user-menu): properly close menu when clicking relay items

Use pointer-events-none on RelayLink to make it purely presentational,
and handle the click on DropdownMenuItem instead. This ensures the
menu closes properly when clicking a relay.

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Alejandro
2026-01-20 19:14:25 +01:00
committed by GitHub
parent 13081938ff
commit cbc1fc272a

View File

@@ -1,6 +1,5 @@
import {
User,
HardDrive,
Palette,
Wallet,
X,
@@ -8,6 +7,8 @@ import {
Eye,
EyeOff,
Zap,
LogIn,
LogOut,
} from "lucide-react";
import accounts from "@/services/accounts";
import { useProfile } from "@/hooks/useProfile";
@@ -370,6 +371,18 @@ export default function UserMenu() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-80" align="start">
{/* Login first for logged out users */}
{!account && (
<>
<DropdownMenuItem onClick={() => setShowLogin(true)}>
<LogIn className="size-4 text-muted-foreground mr-2" />
<span className="text-sm">Log in</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
{/* User Profile - Identity section */}
{account && (
<>
<DropdownMenuGroup>
@@ -385,7 +398,7 @@ export default function UserMenu() {
</>
)}
{/* Wallet Section - Always show */}
{/* Wallet Section */}
{nwcConnection ? (
<DropdownMenuItem
className="cursor-crosshair flex items-center justify-between"
@@ -423,106 +436,63 @@ export default function UserMenu() {
</DropdownMenuItem>
)}
{/* Support Grimoire Section */}
<DropdownMenuSeparator />
<DropdownMenuGroup>
<div
className="px-2 py-2 cursor-crosshair hover:bg-accent/50 transition-colors"
onClick={openDonate}
>
<div className="flex items-center gap-2 mb-3">
<Zap className="size-4 text-yellow-500" />
<span className="text-sm font-medium">Support Grimoire</span>
</div>
<Progress value={goalProgress} className="h-1.5 mb-1" />
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">
<span className="text-foreground font-medium">
{formatSats(monthlyDonations)}
</span>
{" / "}
{formatSats(MONTHLY_GOAL_SATS)}
</span>
<span className="text-muted-foreground">
{goalProgress.toFixed(0)}%
</span>
</div>
</div>
</DropdownMenuGroup>
{/* Account Configuration - Relays & Blossom */}
{account && (
<>
{relays && relays.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
Relays
</DropdownMenuLabel>
{relays.map((relay) => (
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
Relays
</DropdownMenuLabel>
{relays.map((relay) => (
<DropdownMenuItem
key={relay.url}
className="p-0 cursor-crosshair"
onClick={() => addWindow("relay", { url: relay.url })}
>
<RelayLink
className="px-2 py-1"
className="px-2 py-1.5 w-full pointer-events-none"
urlClassname="text-sm"
iconClassname="size-4"
key={relay.url}
url={relay.url}
read={relay.read}
write={relay.write}
/>
))}
</DropdownMenuGroup>
</>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
)}
{blossomServers && blossomServers.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
<HardDrive className="size-3.5" />
<span>Blossom Servers</span>
</DropdownMenuLabel>
{blossomServers.map((server) => (
<DropdownMenuItem
key={server}
className="cursor-crosshair"
onClick={() => {
addWindow(
"blossom",
{ subcommand: "list", serverUrl: server },
`Files on ${server}`,
);
}}
>
<HardDrive className="size-4 text-muted-foreground mr-2" />
<span className="text-sm truncate">{server}</span>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</>
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
Blossom Servers
</DropdownMenuLabel>
{blossomServers.map((server) => (
<DropdownMenuItem
key={server}
className="cursor-crosshair"
onClick={() => {
addWindow(
"blossom",
{ subcommand: "list", serverUrl: server },
`Files on ${server}`,
);
}}
>
<span className="text-sm truncate">{server}</span>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
Log out
</DropdownMenuItem>
</>
)}
{!account && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setShowLogin(true)}>
Log in
</DropdownMenuItem>
</>
)}
{/* Theme Section - Always show */}
{/* App Preferences - Theme */}
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger className="cursor-crosshair">
<Palette className="size-4 mr-2" />
<Palette className="size-4 text-muted-foreground mr-2" />
Theme
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
@@ -544,6 +514,42 @@ export default function UserMenu() {
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
{/* Support Grimoire */}
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-crosshair flex-col items-stretch p-2"
onClick={openDonate}
>
<div className="flex items-center gap-2 mb-3">
<Zap className="size-4 text-yellow-500" />
<span className="text-sm font-medium">Support Grimoire</span>
</div>
<Progress value={goalProgress} className="h-1.5 mb-1" />
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">
<span className="text-foreground font-medium">
{formatSats(monthlyDonations)}
</span>
{" / "}
{formatSats(MONTHLY_GOAL_SATS)}
</span>
<span className="text-muted-foreground">
{goalProgress.toFixed(0)}%
</span>
</div>
</DropdownMenuItem>
{/* Logout at bottom for logged in users */}
{account && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
<LogOut className="size-4 text-muted-foreground mr-2" />
<span className="text-sm">Log out</span>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</>