Skip to content

Commit 0bf464c

Browse files
authored
Merge pull request #87 from Twigpine/fix/header-fit
fix(header): the Launch button fits the desktop header in every wallet state
2 parents ef8b642 + 7316d72 commit 0bf464c

8 files changed

Lines changed: 50 additions & 14 deletions

File tree

‎app/src/components/HeaderNav.tsx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,14 @@ function Rail({ children, className }: { children: React.ReactNode; className?:
9595
}
9696

9797
/**
98-
* The header's way into search: the first tool in the rail, naming its shortcut beside the icon while the strip
99-
* rests at full width (xl and up). The home redesign gives search a proper field.
98+
* The header's way into search: the first tool in the rail, its shortcut named in the tooltip (a ⌘K hint beside the
99+
* icon pushed the Launch button out of the strip). The home redesign gives search a proper field.
100100
*/
101-
function SearchTrigger({ onClick, compact = false }: { onClick: () => void; compact?: boolean }) {
101+
function SearchTrigger({ onClick }: { onClick: () => void }) {
102102
const shortcut = useShortcutLabel();
103103
return (
104-
<button type="button" onClick={onClick} aria-label={`Search tokens and pages (${shortcut})`} title={`Search (${shortcut})`} className="inline-flex h-7.5 min-w-7.5 shrink-0 items-center justify-center gap-1.5 rounded-full px-[7px] text-body transition-colors hover:bg-line hover:text-ink motion-reduce:transition-none">
104+
<button type="button" onClick={onClick} aria-label={`Search tokens and pages (${shortcut})`} title={`Search (${shortcut})`} className="inline-flex h-7.5 min-w-7.5 shrink-0 items-center justify-center rounded-full px-[7px] text-body transition-colors hover:bg-line hover:text-ink motion-reduce:transition-none">
105105
<Search size={16} aria-hidden />
106-
{compact ? null : <kbd aria-hidden="true" className="hidden rounded-md border border-line bg-paper px-1.5 font-sans text-[10px] font-medium leading-4 text-muted xl:inline">{shortcut}</kbd>}
107106
</button>
108107
);
109108
}
@@ -152,10 +151,11 @@ function Desktop({ visible = false, pulse, isActive, quietCta, onSearch }: { vis
152151
<NavLinks isActive={isActive} compact={visible} />
153152

154153
<div className="relative z-20 ml-auto flex items-center gap-2">
154+
{/* icons only, resting or floating: the strip's room is fixed (the page column at rest, the 800px pill), and a
155+
"Bridge" label or a ⌘K hint pushed the Launch button out of it */}
155156
<Rail>
156-
<SearchTrigger onClick={onSearch} compact={visible} />
157-
{/* the floating pill is 800px wide: the bridge gives up its label there */}
158-
<BridgeButton compact={visible} />
157+
<SearchTrigger onClick={onSearch} />
158+
<BridgeButton compact />
159159
<NotificationSettings />
160160
<ThemeToggle />
161161
</Rail>

‎app/src/components/WalletMenu.module.css‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
.trigger:hover, .trigger[data-popup-open] { border-color: var(--color-line-strong); background: var(--color-line); }
1717
.triggerMark { position: relative; display: flex; flex-shrink: 0; }
1818
.connectionDot { position: absolute; right: 0; bottom: 0; width: 7px; height: 7px; background: var(--color-up); border: 2px solid var(--color-card); border-radius: 50%; }
19+
/* the header chip on a network the site doesn't support: the corner the chain's mark would take, ringed like it */
20+
.networkWarn { position: absolute; right: -6px; bottom: -6px; display: grid; place-items: center; width: 18px; height: 18px; border: 2px solid var(--color-card); border-radius: 50%; background: var(--color-warm); color: var(--color-card); font: 700 9px/1 var(--font-sans); }
1921
.triggerAddress { font: 400 12px var(--font-code); font-variant-numeric: tabular-nums; }
2022
.triggerNetwork { padding-left: 8px; border-left: 1px solid var(--color-line-strong); font: 500 10px var(--font-sans); color: var(--color-muted); }
2123
.chevron { color: var(--color-muted); transition: transform 160ms; }

‎app/src/components/WalletMenu.tsx‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CHAIN_LABELS, CHAIN_SHORT, chainKeyOf, explorerAddress, explorerName, s
99
import { BRIDGE_CHAINS, isBridgeChainId } from "@/lib/bridge/types";
1010
import { VISIBLE_CHAINS } from "@/lib/launchpad/config";
1111
import WalletAvatar from "./WalletAvatar";
12+
import { ChainLogo } from "./launchpad/ChainLogo";
1213
import styles from "./WalletMenu.module.css";
1314

1415
/** Official network marks (app/public/brand). A chain with a dark-mode variant lists both; the CSS swaps them by theme. */
@@ -30,6 +31,16 @@ function NetworkLogo({ chain }: { chain: ChainKey }) {
3031
);
3132
}
3233

34+
/**
35+
* The header chip's network, pinned to the avatar's corner like the chain on a token avatar, or a warm "!" on a
36+
* network the site doesn't support. The chip's name for it pushed the Launch button out of the header, so only the
37+
* mobile row spells it out.
38+
*/
39+
function NetworkBadge({ chain }: { chain: ChainKey | null }) {
40+
if (!chain) return <span className={styles.networkWarn} aria-hidden>!</span>;
41+
return <ChainLogo chain={chain} size={14} className="absolute -right-1 -bottom-1 ring-2 ring-card" />;
42+
}
43+
3344
type WalletMenuProps = {
3445
address: string;
3546
chainId?: number;
@@ -100,11 +111,12 @@ function AccountMenu({ address, chainId, connectorName, block = false, switching
100111
<Popover.Trigger
101112
className={`${styles.trigger} ${block ? styles.block : ""}`}
102113
aria-label={`Wallet ${shortAddr(address)}, ${network}. Open account menu`}
114+
title={block ? undefined : key || bridgeNetwork ? `On ${network}` : "Unsupported network. Open to switch"}
103115
data-unsupported={(!key && !bridgeNetwork) || undefined}
104116
>
105-
<span className={styles.triggerMark}><WalletAvatar address={address} /><span className={styles.connectionDot} /></span>
117+
<span className={styles.triggerMark}><WalletAvatar address={address} />{block ? <span className={styles.connectionDot} /> : <NetworkBadge chain={key ?? bridgeNetwork?.key ?? null} />}</span>
106118
<span className={styles.triggerAddress}>{shortAddr(address)}</span>
107-
<span className={styles.triggerNetwork}>{key || bridgeNetwork ? network : "Switch"}</span>
119+
{block ? <span className={styles.triggerNetwork}>{key || bridgeNetwork ? network : "Switch"}</span> : null}
108120
<ChevronDown size={13} className={styles.chevron} aria-hidden />
109121
</Popover.Trigger>
110122
<Popover.Portal>

‎app/src/components/bridge/BridgeDialog.module.css‎

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎app/src/components/bridge/BridgeProvider.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function useOpenBridge(): (() => void) | null {
3535
return useContext(BridgeContext);
3636
}
3737

38-
/** `compact` drops the label (icon only) where the header has no room, as in the floating pill. */
38+
/** `compact` drops the label (icon only) where the header has no room: the whole desktop strip, resting or floating. */
3939
export function BridgeButton({ block = false, compact = false, onOpen }: { block?: boolean; compact?: boolean; onOpen?: () => void }) {
4040
const show = useContext(BridgeContext);
4141
return (

‎app/src/components/bridge/bridge-ui.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ test("bridge uses bundled official asset marks rather than letter placeholders",
2525

2626
test("bridge keeps one lazily loaded controller across desktop/mobile and dismissal", () => {
2727
assert.match(header, /<BridgeProvider>/);
28-
// desktop: one button, icon-only while the header floats (the pill also carries search)
29-
assert.match(header, /<BridgeButton compact=\{visible\} \/>/);
28+
// desktop: one button, icon-only (its label pushed the Launch button out of the strip)
29+
assert.match(header, /<BridgeButton compact \/>/);
3030
assert.match(header, /<BridgeButton block onOpen=\{\(\) => setOpen\(false\)\} \/>/);
3131
assert.match(provider, /dynamic\(\(\) => import\("\.\/BridgeDialog"\)/);
3232
assert.match(provider, /activated \? <BridgeDialog/);

‎app/src/components/header-nav.test.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ test("mobile Launch closes its menu and route changes reset persistent header st
1717
assert.match(header, /quietCta=\{heroCtaOnScreen\}/, "the hero/header CTA handoff is preserved");
1818
});
1919

20+
test("the desktop tools stay icons so the Launch button fits the strip", () => {
21+
// the strip's room is fixed (the page column at rest, the 800px pill floating); a ⌘K hint or a "Bridge" label pushed
22+
// the Launch button past it. The fit itself is checked in a browser (1024–2560px, every wallet state).
23+
const desktop = header.slice(header.indexOf("function Desktop"), header.indexOf("function NavLinks"));
24+
assert.match(desktop, /<SearchTrigger onClick=\{onSearch\} \/>/);
25+
assert.match(desktop, /<BridgeButton compact \/>/);
26+
assert.doesNotMatch(header, /<kbd\b/);
27+
assert.match(navbar, /style=\{mobile \? undefined : \{ minWidth: 800 \}\}/);
28+
});
29+
2030
test("Escape dismisses only an unconsumed menu event and returns focus to its toggle", () => {
2131
assert.match(navbar, /event\.key === "Escape" && !event\.defaultPrevented/);
2232
assert.match(navbar, /event\.preventDefault\(\);\s+onClose\(\)/);

‎app/src/components/wallet-menu.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ test("network switcher uses official local logos with theme-correct Robinhood ma
7575
assert.match(css, /:global\(\.dark\) \.networkLogo \.darkLogo\s*\{\s*display: block/);
7676
});
7777

78+
test("the header chip carries its network as an avatar badge; only the mobile row spells it out", () => {
79+
// the network's name in the desktop chip pushed the Launch button out of the header (up to 49px in the floating pill)
80+
const trigger = source.slice(source.indexOf("<Popover.Trigger"), source.indexOf("</Popover.Trigger>"));
81+
assert.match(trigger, /\{block \? <span className=\{styles\.triggerNetwork\}>/);
82+
assert.match(trigger, /block \? <span className=\{styles\.connectionDot\} \/> : <NetworkBadge chain=\{key \?\? bridgeNetwork\?\.key \?\? null\} \/>/);
83+
assert.match(trigger, /title=\{block \? undefined : [^\n]*"Unsupported network\. Open to switch"\}/);
84+
const badge = source.slice(source.indexOf("function NetworkBadge"), source.indexOf("type WalletMenuProps"));
85+
assert.match(badge, /if \(!chain\) return <span className=\{styles\.networkWarn\} aria-hidden>!<\/span>/);
86+
// the site's chain tile (Arc's white mark needs its dark tile), as on token avatars
87+
assert.match(badge, /<ChainLogo chain=\{chain\} size=\{14\} className="absolute -right-1 -bottom-1 ring-2 ring-card" \/>/);
88+
assert.match(css, /\.networkWarn\s*\{[^}]*position: absolute[^}]*background: var\(--color-warm\)/);
89+
});
90+
7891
test("wallet shortcuts copy the full address and close the nested menu on navigation", () => {
7992
assert.match(source, /await navigator.clipboard.writeText\(address\)/);
8093
assert.match(source, /Address copied\./);

0 commit comments

Comments
 (0)