feat: replace the account popover with two plain wallet buttons - #136
Conversation
`ConnectButton` was one component that swapped between a connect trigger and a Zag popover holding the account, with a `mode` prop to pin it to one. A host that wants a dropdown of its own had to take ours or take nothing. `WalletButton/` replaces it with three exports from `/connect`: `ConnectButton` and `DisconnectButton`, each a plain button that renders whatever the session says and calls one method, and `WalletButton`, a chooser that follows `useWalletStatus().isConnected` and hands its props to the face it picks. The session and not the party decides that face, because a locked wallet keeps the session and clears the party. The popover goes with it, and `@zag-js/popover` with the popover: no component here needs a popper any more. `LogoutIcon` and `ChevronDownIcon` were rendered only inside it.
`.cnc-disconnect-button` shares the connect button's rules, since they are the same object with different words. The popover's own block goes with the component, taking the second `z-index` value and the only `[hidden]` rule in the file with it. Two rules the file was already following go into CLAUDE.md while there is a case to point at: hoist a shared trailing part into `:is()` where every entry weighs the same, and never nest with `&`, which hides a class from the regex `check:anatomy` harvests selectors with.
The kit's account popover was the whole of the connected face, so the app could show the party id and nothing else. `TopBar/AccountMenu` replaces it with a dropdown the app owns: a copyable party id, the network the session is on, and the kit's `DisconnectButton`. The top bar picks the face rather than mounting the kit's `WalletButton`, on the party or the lock and not on `isConnected` alone. A standing session reports no party while the account read is in flight and again after it fails, and the connect face answers both — pending copy for the first, a retry for the second. A lock is the one state that clears the party for good, and there the dropdown is replaced whole by a disabled button. `useParty` reports that lock beside the party, so nothing else in the app reaches for a canton-connect hook. Dismissal is `hooks/useDismissable`, shared with `RoleSelect`: the focusout test against the root, the Escape close, the focus returned to the trigger, and the mousedown guard without which Safari unmounts the panel before the click lands.
The shell ended in a hardcoded status strip. `Footer` ports dappbooster.dev's: the BootNode credit, the four social links and the two resource links, over one outbound anchor and six new icons.
`ConnectButton` was one component in every doc that named it, and its popover was the example for how Zag earns its place and how depth is decided. Both seams move: the kit's is three components behind `/connect` that require a provider ancestor, the app's is a top bar picking a face and an account menu holding the session. The shared pieces the app now has one of each of are listed with them, and the published reference is at its own domain rather than the Vercel one.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR replaces the kit's hardcoded, @zag-js/popover-based account popover inside ConnectButton with three plain, composable buttons on the /connect sub-path: ConnectButton, DisconnectButton, and a WalletButton chooser that follows the session. This lets a consumer build its own dropdown/drawer by wrapping the buttons and opting out of the default action with preventDefault(). The app's dapp/frontend is the sole consumer and is updated to rebuild the connected-account dropdown itself in TopBar/AccountMenu, backed by a new shared useDismissable hook (also adopted by RoleSelect). The change also ports a richer footer and renames the theme's .cnc-account-* rules to .cnc-disconnect-button.
Changes:
/connectnow shipsConnectButton,DisconnectButton, andWalletButton;mode,ConnectButtonMode,avatar, the popover, and@zag-js/popoverare removed.- The app rebuilds the account dropdown in
TopBar/AccountMenu(copyable party id, network, disconnect; disabled "Unlock your wallet" when locked) and extracts blur/Escape dismissal intohooks/useDismissable. - Supporting changes:
usePartynow surfacesisLockedandnetworkId; a newFooterwith six icons replaces the status strip; theme selectors are hoisted into:is()and renamed; docs/URLs updated.
Reviewed changes
Copilot reviewed 42 out of 44 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
canton-dappbooster/src/components/WalletButton/{index,ConnectButton,DisconnectButton}.tsx |
New session-following chooser plus the two plain button faces. |
canton-dappbooster/src/components/WalletButton/{anatomy,composeAction}.ts |
Split connect/disconnect anatomies and the shared preventDefault-aware action composer. |
canton-dappbooster/src/components/WalletButton/*.test.tsx |
Tests for the chooser, connect and disconnect faces. |
canton-dappbooster/src/components/ConnectButton/* (deleted) |
Old popover-based button, anatomy, tests, and AccountPopover removed. |
canton-dappbooster/src/connect.ts |
Barrel now exports the three wallet buttons and their prop types. |
canton-dappbooster/src/icons/{ChevronDownIcon,LogoutIcon}.tsx + index.ts |
Removed now-unused kit icons. |
canton-dappbooster/{package.json,doc-fixtures.d.ts,README.md,CLAUDE.md,architecture.md} |
Drop @zag-js/popover, add toggleMenu fixture, update docs. |
canton-theme/src/default.css, canton-theme/CLAUDE.md |
Rename to .cnc-disconnect-button, hoist selectors into :is(), document specificity/z-index rules. |
dapp/frontend/src/components/TopBar/{index,AccountMenu}.tsx |
Rebuild the connected dropdown and pick the face on party/lock. |
dapp/frontend/src/hooks/{useDismissable,useParty}.ts, components/RoleSelect.tsx |
Shared dismissal hook; useParty adds isLocked/networkId; RoleSelect adopts the hook. |
dapp/frontend/src/components/{Footer,AppShell,ConnectPrompt,EmptyState}.tsx + src/icons/* |
New footer with social/resource icons replacing the status strip; drop mode. |
canton-connect/README.md, pnpm-lock.yaml |
Docs URL update; lockfile drops popover/popper. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
fernandomg
left a comment
There was a problem hiding this comment.
some tiny things that need fixing, an open question regarding AccountMenu
A wallet reports a lock and its own disconnect as the same push, so the header cannot know which it is. The disabled button also disagreed with the refresh path, which lands on the connect face for the same wallet state.
TopBar already picks between the two faces on party, so the menu re-reading the hook only to return null was a second gate on the same condition.
The parent decides between the menu and the connect button, so the menu always renders.
Summary
Closes #135
The kit's
ConnectButtonowned a hardcoded account popover, so a consumer could not swap it for a drawer, restyle it, or reduce it to a plain disconnect button. The trigger'sonClickbelonged to the popover and the panel was internal.Review guide
Worth your time:
WalletButton: a chooser over the two faces, with no caller in this repoTopBar/AccountMenuanduseDismissable: hand-rolled dismissal, focus and ARIATopBar's face rule: party or lock, notisConnectedSkim:
canton-theme: a rename plus a selector hoistChanges
/connectshipsConnectButton,DisconnectButtonandWalletButton, a chooser that follows the sessionchildrenreplace the label on both faces, pending copy includedpreventDefault()in a consumeronClickcancels connect and disconnectmode,ConnectButtonModeand@zag-js/popoverare goneuseDismissablecarries the blur-and-Escape dismissal both app dropdowns shareDeviations
AccountButtonships asDisconnectButton; it shows no party, so it is named for its actionWalletButtonpicks between them, so either face can go anywhere.cnc-disconnect-button, following the renameAppShell's hardcoded status stripcanton-connectREADME, and a staleEmptyStatecommentAcceptance criteria
<AccountButton>reads the party fromuseParty()and takes nopartyIdprop<ConnectButton>and<AccountButton>each render null in the other session statemodeandConnectButtonModeare gone from the/connectbarrelchildrenreplace the label on both buttons, pending copy includedpreventDefault()in a consumeronClickcancels connect and disconnect respectivelyAccountPopover.tsx,popoverAnatomyandstates.modeare deleted@zag-js/popover,LogoutIconandChevronDownIconare removed from the kit.cnc-account-popover*rules leavecanton-theme, shared reset and focus lists included.cnc-account-buttonis styled, sopnpm check:anatomypassesdapp/frontendrebuilds the dropdown inTopBar/AccountMenu.tsxwith no new dependencyConnectPromptno longer passesmodepnpm lint,test,typecheck,build,knip,docs:checkandcheck:anatomypassTest plan
Automated tests
pnpm installpnpm lint,pnpm typecheck,pnpm test,pnpm build,pnpm knip,pnpm docs:checkandpnpm run check:anatomyManual verification
pnpm app:devBreaking changes
/connectdropsmodeandavatarand adds two exports. The package is private and unpublished, and its only consumer is updated here.Checklist
Screenshots
Should look mostly the same as it did before.