From d29570a1221d0d8e01da81cf4aac4af588c62340 Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Mon, 10 Aug 2026 11:24:38 -0600 Subject: [PATCH] feat(ui): add Web3 wallets section --- .../user-profile-web3-wallets-section.md | 2 + .../swingset/src/components/DocsViewer.tsx | 1 + packages/swingset/src/lib/registry.ts | 9 ++ .../user-profile-web3-wallets-section.mdx | 14 +++ ...r-profile-web3-wallets-section.stories.tsx | 32 +++++ .../user-profile-profile-panel.styles.ts | 11 ++ ...user-profile-web3-wallets-section.view.tsx | 117 ++++++++++++++++++ 7 files changed, 186 insertions(+) create mode 100644 .changeset/user-profile-web3-wallets-section.md create mode 100644 packages/swingset/src/stories/user-profile-web3-wallets-section.mdx create mode 100644 packages/swingset/src/stories/user-profile-web3-wallets-section.stories.tsx create mode 100644 packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx diff --git a/.changeset/user-profile-web3-wallets-section.md b/.changeset/user-profile-web3-wallets-section.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/user-profile-web3-wallets-section.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/swingset/src/components/DocsViewer.tsx b/packages/swingset/src/components/DocsViewer.tsx index 014aa89f1f9..9bf2adc49be 100644 --- a/packages/swingset/src/components/DocsViewer.tsx +++ b/packages/swingset/src/components/DocsViewer.tsx @@ -16,6 +16,7 @@ const docModules: Record> = { 'user-profile-connected-accounts-section': dynamic( () => import('../stories/user-profile-connected-accounts-section.mdx'), ), + 'user-profile-web3wallets-section': dynamic(() => import('../stories/user-profile-web3-wallets-section.mdx')), }, organization: { 'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')), diff --git a/packages/swingset/src/lib/registry.ts b/packages/swingset/src/lib/registry.ts index 3a8262e6528..e5b441abb9a 100644 --- a/packages/swingset/src/lib/registry.ts +++ b/packages/swingset/src/lib/registry.ts @@ -138,6 +138,10 @@ import { Default as UserProfileConnectedAccountsSectionDefault, meta as userProfileConnectedAccountsSectionMeta, } from '../stories/user-profile-connected-accounts-section.stories'; +import { + Default as UserProfileWeb3WalletsSectionDefault, + meta as userProfileWeb3WalletsSectionMeta, +} from '../stories/user-profile-web3-wallets-section.stories'; import { toSlug } from './slug'; import type { StoryModule } from './types'; @@ -287,12 +291,17 @@ const userProfileConnectedAccountsSectionModule: StoryModule = { meta: userProfileConnectedAccountsSectionMeta, Default: UserProfileConnectedAccountsSectionDefault, }; +const userProfileWeb3WalletsSectionModule: StoryModule = { + meta: userProfileWeb3WalletsSectionMeta, + Default: UserProfileWeb3WalletsSectionDefault, +}; export const registry: StoryModule[] = [ // User userButtonModule, userProfileAccountSectionModule, userProfileConnectedAccountsSectionModule, + userProfileWeb3WalletsSectionModule, // Organization organizationProfileModule, organizationProfileGeneralPanelModule, diff --git a/packages/swingset/src/stories/user-profile-web3-wallets-section.mdx b/packages/swingset/src/stories/user-profile-web3-wallets-section.mdx new file mode 100644 index 00000000000..1f6661e4aa8 --- /dev/null +++ b/packages/swingset/src/stories/user-profile-web3-wallets-section.mdx @@ -0,0 +1,14 @@ +import * as Stories from './user-profile-web3-wallets-section.stories'; + +# UserProfileWeb3WalletsSection + +Web3 wallet identities, verification state, primary status, and wallet actions. + + diff --git a/packages/swingset/src/stories/user-profile-web3-wallets-section.stories.tsx b/packages/swingset/src/stories/user-profile-web3-wallets-section.stories.tsx new file mode 100644 index 00000000000..e21da2cdb43 --- /dev/null +++ b/packages/swingset/src/stories/user-profile-web3-wallets-section.stories.tsx @@ -0,0 +1,32 @@ +/** @jsxImportSource @emotion/react */ +import { UserProfileWeb3WalletsSectionView } from '@clerk/ui/mosaic/user-profile/user-profile-web3-wallets-section.view'; + +import type { StoryMeta } from '@/lib/types'; + +export { default as __source } from './user-profile-web3-wallets-section.stories?raw'; + +export const meta: StoryMeta = { + group: 'User', + title: 'UserProfileWeb3WalletsSection', + source: 'packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx', + styleEngine: 'stylex', +}; + +export function Default() { + return ( + undefined} + onManage={() => undefined} + /> + ); +} diff --git a/packages/ui/src/mosaic/user-profile/user-profile-profile-panel.styles.ts b/packages/ui/src/mosaic/user-profile/user-profile-profile-panel.styles.ts index 23d0cdb4f00..a3db8d85f35 100644 --- a/packages/ui/src/mosaic/user-profile/user-profile-profile-panel.styles.ts +++ b/packages/ui/src/mosaic/user-profile/user-profile-profile-panel.styles.ts @@ -15,6 +15,17 @@ export const styles = stylex.create({ height: space['6'], width: space['6'], }, + sectionHeader: { + alignItems: 'flex-end', + columnGap: space['4'], + display: 'flex', + justifyContent: 'space-between', + minWidth: 0, + }, + sectionTitle: { + flexGrow: 1, + minWidth: 0, + }, root: { gap: space['6'], display: 'flex', diff --git a/packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx b/packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx new file mode 100644 index 00000000000..c69469515cb --- /dev/null +++ b/packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx @@ -0,0 +1,117 @@ +import * as stylex from '@stylexjs/stylex'; + +import { Badge } from '../components/badge'; +import { Button } from '../components/button'; +import { Settings } from '../components/settings'; +import type { UserProfileMenuAction } from './user-profile-action-menu'; +import { UserProfileActionMenu } from './user-profile-action-menu'; +import { styles } from './user-profile-profile-panel.styles'; + +export interface UserProfileWeb3Wallet { + id: string; + address: string; + provider?: string; + iconUrl?: string; + isPrimary?: boolean; + isVerified?: boolean; + canRemove?: boolean; +} + +export interface UserProfileWeb3WalletsSectionViewProps { + wallets: UserProfileWeb3Wallet[]; + onAdd?: () => void; + onManage?: (id: string) => void; + onSetPrimary?: (id: string) => void; + onRemove?: (id: string) => void; +} + +const shortenWeb3Address = (address: string) => { + if (address.length <= 10) { + return address; + } + + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export function UserProfileWeb3WalletsSectionView({ + wallets, + onAdd, + onManage, + onSetPrimary, + onRemove, +}: UserProfileWeb3WalletsSectionViewProps) { + return ( + +
+ Web3 wallets + {onAdd ? ( + + ) : null} +
+ + {wallets.map(wallet => { + const actions: UserProfileMenuAction[] = []; + const hasExplicitActions = Boolean(onSetPrimary || onRemove); + + if (!wallet.isPrimary && wallet.isVerified !== false && onSetPrimary) { + actions.push({ label: 'Set as primary', onClick: () => onSetPrimary(wallet.id) }); + } + + if (onRemove && wallet.canRemove !== false) { + actions.push({ label: 'Remove wallet', color: 'negative', onClick: () => onRemove(wallet.id) }); + } + + if (!hasExplicitActions && onManage) { + actions.push({ label: 'Manage', onClick: () => onManage(wallet.id) }); + } + + const address = shortenWeb3Address(wallet.address); + const badges = ( + <> + {wallet.isPrimary ? Primary : null} + {wallet.isVerified === false ? Unverified : null} + + ); + + return ( + + + {wallet.iconUrl ? ( + + + + ) : null} + + + + {wallet.provider ?? address} + {badges} + + + {wallet.provider ? {address} : null} + + + + + + + ); + })} + +
+ ); +}