@bufinance/web3-signin 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/react/index.ts +8 -0
- package/src/react/wallet-balance-panel.tsx +232 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bufinance/web3-signin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Headless cross-app Web3 wallet sign-in for BUFI: EIP-6963 wallet discovery + Supabase signInWithWeb3 (EIP-4361) + Turnstile captcha. Shared by desk-v1 and defi-web-app. Source of truth lives here; both apps consume it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/react/index.ts
CHANGED
|
@@ -16,3 +16,11 @@ export {
|
|
|
16
16
|
type AssetCircleItem,
|
|
17
17
|
type AssetCirclesProps,
|
|
18
18
|
} from "./avatar-circles";
|
|
19
|
+
export {
|
|
20
|
+
WalletBalancePanel,
|
|
21
|
+
type WalletBalancePanelProps,
|
|
22
|
+
type WalletBalancePanelChain,
|
|
23
|
+
type WalletBalancePanelToken,
|
|
24
|
+
type WalletBalancePanelTab,
|
|
25
|
+
type WalletBalancePanelClassNames,
|
|
26
|
+
} from "./wallet-balance-panel";
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WalletBalancePanel — the shared, presentational body of the wallet dropdown:
|
|
5
|
+
* header (title + network tabs + optional scope tabs + total + ghost) and a
|
|
6
|
+
* per-chain token list. Ported from defi-web's inline dropdown body so desk +
|
|
7
|
+
* defi-web render the SAME body (Phase 1 of the shared <BufiWallet>).
|
|
8
|
+
*
|
|
9
|
+
* Presentational + injected: the host supplies the chain/token data, the live
|
|
10
|
+
* total + ghost as ReactNodes (so it can keep animated counters), and theming
|
|
11
|
+
* via `classNames` (each app's tokens differ). No data-fetching, no app deps.
|
|
12
|
+
*/
|
|
13
|
+
export interface WalletBalancePanelToken {
|
|
14
|
+
asset: string;
|
|
15
|
+
/** Already display-formatted amount, e.g. "12.45". */
|
|
16
|
+
amount: string;
|
|
17
|
+
iconUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WalletBalancePanelChain {
|
|
21
|
+
chainId: number;
|
|
22
|
+
label: string;
|
|
23
|
+
iconUrl?: string;
|
|
24
|
+
/** Net-filtered, host-decided which tokens to show (e.g. nonzero only). */
|
|
25
|
+
tokens: WalletBalancePanelToken[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WalletBalancePanelTab {
|
|
29
|
+
key: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WalletBalancePanelClassNames {
|
|
34
|
+
header?: string;
|
|
35
|
+
headerRow?: string;
|
|
36
|
+
titleWrap?: string;
|
|
37
|
+
titleIcon?: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
netRow?: string;
|
|
40
|
+
netTab?: string;
|
|
41
|
+
netTabActive?: string;
|
|
42
|
+
scopeRow?: string;
|
|
43
|
+
scopeTab?: string;
|
|
44
|
+
scopeTabActive?: string;
|
|
45
|
+
totalRow?: string;
|
|
46
|
+
totalLabel?: string;
|
|
47
|
+
totalValue?: string;
|
|
48
|
+
ghostRow?: string;
|
|
49
|
+
body?: string;
|
|
50
|
+
empty?: string;
|
|
51
|
+
chainRow?: string;
|
|
52
|
+
chainIcon?: string;
|
|
53
|
+
chainLabel?: string;
|
|
54
|
+
tokenList?: string;
|
|
55
|
+
tokenRow?: string;
|
|
56
|
+
tokenIcon?: string;
|
|
57
|
+
tokenZero?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface WalletBalancePanelProps {
|
|
61
|
+
title: string;
|
|
62
|
+
/** "Personal" / "Treasury" — labels the total row. */
|
|
63
|
+
scopeLabel?: string;
|
|
64
|
+
/** Live total node (host renders e.g. "≈ $<AnimatedNumber/>"), or "—". */
|
|
65
|
+
totalText: ReactNode;
|
|
66
|
+
/** False → render the empty state instead of the chain list. */
|
|
67
|
+
hasAddress?: boolean;
|
|
68
|
+
emptyMessage?: ReactNode;
|
|
69
|
+
/** Small wallet icon before the title. */
|
|
70
|
+
titleIcon?: ReactNode;
|
|
71
|
+
/** Extra node beside the title (e.g. <AssetCircles>). */
|
|
72
|
+
titleExtra?: ReactNode;
|
|
73
|
+
/** Network tabs (mainnet/testnet); omit to hide. */
|
|
74
|
+
networks?: WalletBalancePanelTab[];
|
|
75
|
+
activeNetwork?: string;
|
|
76
|
+
onNetworkChange?: (key: string) => void;
|
|
77
|
+
/** Scope tabs (team: Treasury/Operations); omit to hide. */
|
|
78
|
+
scopes?: WalletBalancePanelTab[];
|
|
79
|
+
activeScope?: string;
|
|
80
|
+
onScopeChange?: (key: string) => void;
|
|
81
|
+
/** Ghost (private) balance row node; omit to hide. */
|
|
82
|
+
ghost?: ReactNode;
|
|
83
|
+
/** Per-chain balances, already net-filtered by the host. */
|
|
84
|
+
chains: WalletBalancePanelChain[];
|
|
85
|
+
/** Per-token cell renderer override (host can pass its own currency cell). */
|
|
86
|
+
renderToken?: (token: WalletBalancePanelToken, chain: WalletBalancePanelChain) => ReactNode;
|
|
87
|
+
/** Shown when a chain has no nonzero tokens (default "$0.00"). */
|
|
88
|
+
zeroText?: ReactNode;
|
|
89
|
+
classNames?: WalletBalancePanelClassNames;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function Tabs({
|
|
93
|
+
tabs,
|
|
94
|
+
active,
|
|
95
|
+
onChange,
|
|
96
|
+
rowClass,
|
|
97
|
+
tabClass,
|
|
98
|
+
activeClass,
|
|
99
|
+
}: {
|
|
100
|
+
tabs: WalletBalancePanelTab[];
|
|
101
|
+
active?: string;
|
|
102
|
+
onChange?: (k: string) => void;
|
|
103
|
+
rowClass?: string;
|
|
104
|
+
tabClass?: string;
|
|
105
|
+
activeClass?: string;
|
|
106
|
+
}) {
|
|
107
|
+
return (
|
|
108
|
+
<div className={rowClass} role="tablist">
|
|
109
|
+
{tabs.map((t) => {
|
|
110
|
+
const on = t.key === active;
|
|
111
|
+
return (
|
|
112
|
+
<button
|
|
113
|
+
key={t.key}
|
|
114
|
+
type="button"
|
|
115
|
+
role="tab"
|
|
116
|
+
aria-selected={on}
|
|
117
|
+
onClick={() => onChange?.(t.key)}
|
|
118
|
+
className={`${tabClass ?? ""} ${on ? activeClass ?? "" : ""}`.trim()}
|
|
119
|
+
>
|
|
120
|
+
{t.label}
|
|
121
|
+
</button>
|
|
122
|
+
);
|
|
123
|
+
})}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function WalletBalancePanel({
|
|
129
|
+
title,
|
|
130
|
+
scopeLabel,
|
|
131
|
+
totalText,
|
|
132
|
+
hasAddress = true,
|
|
133
|
+
emptyMessage,
|
|
134
|
+
titleIcon,
|
|
135
|
+
titleExtra,
|
|
136
|
+
networks,
|
|
137
|
+
activeNetwork,
|
|
138
|
+
onNetworkChange,
|
|
139
|
+
scopes,
|
|
140
|
+
activeScope,
|
|
141
|
+
onScopeChange,
|
|
142
|
+
ghost,
|
|
143
|
+
chains,
|
|
144
|
+
renderToken,
|
|
145
|
+
zeroText = "$0.00",
|
|
146
|
+
classNames = {},
|
|
147
|
+
}: WalletBalancePanelProps): ReactNode {
|
|
148
|
+
return (
|
|
149
|
+
<div data-bufi-balance-panel>
|
|
150
|
+
<div className={classNames.header}>
|
|
151
|
+
<div className={classNames.headerRow}>
|
|
152
|
+
<div className={classNames.titleWrap}>
|
|
153
|
+
{titleIcon ? <span className={classNames.titleIcon}>{titleIcon}</span> : null}
|
|
154
|
+
<span className={classNames.title}>{title}</span>
|
|
155
|
+
{titleExtra}
|
|
156
|
+
</div>
|
|
157
|
+
{networks && networks.length > 0 ? (
|
|
158
|
+
<Tabs
|
|
159
|
+
tabs={networks}
|
|
160
|
+
active={activeNetwork}
|
|
161
|
+
onChange={onNetworkChange}
|
|
162
|
+
rowClass={classNames.netRow}
|
|
163
|
+
tabClass={classNames.netTab}
|
|
164
|
+
activeClass={classNames.netTabActive}
|
|
165
|
+
/>
|
|
166
|
+
) : null}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{scopes && scopes.length > 1 ? (
|
|
170
|
+
<Tabs
|
|
171
|
+
tabs={scopes}
|
|
172
|
+
active={activeScope}
|
|
173
|
+
onChange={onScopeChange}
|
|
174
|
+
rowClass={classNames.scopeRow}
|
|
175
|
+
tabClass={classNames.scopeTab}
|
|
176
|
+
activeClass={classNames.scopeTabActive}
|
|
177
|
+
/>
|
|
178
|
+
) : null}
|
|
179
|
+
|
|
180
|
+
<div className={classNames.totalRow}>
|
|
181
|
+
<span className={classNames.totalLabel}>{scopeLabel ? `${scopeLabel} balance` : "Balance"}</span>
|
|
182
|
+
<span className={classNames.totalValue}>{totalText}</span>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{ghost ? <div className={classNames.ghostRow}>{ghost}</div> : null}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div className={classNames.body}>
|
|
189
|
+
{!hasAddress ? (
|
|
190
|
+
<div className={classNames.empty}>{emptyMessage}</div>
|
|
191
|
+
) : (
|
|
192
|
+
chains.map((c) => (
|
|
193
|
+
<div key={c.chainId} className={classNames.chainRow}>
|
|
194
|
+
<span className={classNames.chainIcon}>
|
|
195
|
+
{c.iconUrl ? (
|
|
196
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
197
|
+
<img src={c.iconUrl} alt="" width={16} height={16} />
|
|
198
|
+
) : (
|
|
199
|
+
c.label.slice(0, 2)
|
|
200
|
+
)}
|
|
201
|
+
</span>
|
|
202
|
+
<div style={{ minWidth: 0, flex: 1 }}>
|
|
203
|
+
<div className={classNames.chainLabel}>{c.label}</div>
|
|
204
|
+
<div className={classNames.tokenList}>
|
|
205
|
+
{c.tokens.length > 0 ? (
|
|
206
|
+
c.tokens.map((t) =>
|
|
207
|
+
renderToken ? (
|
|
208
|
+
<span key={t.asset}>{renderToken(t, c)}</span>
|
|
209
|
+
) : (
|
|
210
|
+
<span key={t.asset} className={classNames.tokenRow}>
|
|
211
|
+
{t.iconUrl ? (
|
|
212
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
213
|
+
<img src={t.iconUrl} alt="" width={14} height={14} className={classNames.tokenIcon} />
|
|
214
|
+
) : null}
|
|
215
|
+
{t.amount} {t.asset}
|
|
216
|
+
</span>
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
) : (
|
|
220
|
+
<span className={classNames.tokenZero}>{zeroText}</span>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
))
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default WalletBalancePanel;
|