@bufinance/web3-signin 0.1.3 → 0.1.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bufinance/web3-signin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",
@@ -0,0 +1,134 @@
1
+ import { type CSSProperties, useState } from "react";
2
+
3
+ /**
4
+ * AssetCircles — overlapping token/asset icon circles that arc up and reveal a
5
+ * ticker tooltip on hover, with a "+N" overflow chip. Ported from desk-v1's
6
+ * `WalletTokenIcons` (the "which assets does this wallet hold" affordance) into
7
+ * the shared package so BOTH apps render assets the same way. This is the
8
+ * asset/token stack (icon + ticker tooltip) — NOT a people/profile stack.
9
+ *
10
+ * Dependency-light: inline styles only (no Tailwind purge risk, no framer/radix),
11
+ * so it drops into any consumer unchanged. The host supplies `assets`.
12
+ */
13
+ export interface AssetCircleItem {
14
+ /** Token icon URL. */
15
+ iconUrl: string;
16
+ /** Ticker shown in the tooltip + used as alt text (e.g. "USDC"). */
17
+ label: string;
18
+ }
19
+
20
+ export interface AssetCirclesProps {
21
+ assets: AssetCircleItem[];
22
+ /** Max circles before collapsing the rest into a "+N" chip (default 4). */
23
+ max?: number;
24
+ /** Circle diameter in px (default 18, tuned for a header pill). */
25
+ size?: number;
26
+ className?: string;
27
+ }
28
+
29
+ export function AssetCircles({
30
+ assets,
31
+ max = 4,
32
+ size = 18,
33
+ className,
34
+ }: AssetCirclesProps) {
35
+ const [hover, setHover] = useState<number | null>(null);
36
+ if (assets.length === 0) return null;
37
+
38
+ const shown = assets.slice(0, max);
39
+ const overflow = assets.length - shown.length;
40
+ const overlap = Math.round(size / 3);
41
+ const lift = Math.max(5, Math.round(size * 0.4));
42
+
43
+ return (
44
+ <div
45
+ className={className}
46
+ style={{ display: "inline-flex", alignItems: "center" }}
47
+ data-bufi-asset-circles
48
+ >
49
+ {shown.map((a, i) => {
50
+ const h = hover === i;
51
+ return (
52
+ <span
53
+ key={`${a.label}:${i}`}
54
+ onMouseEnter={() => setHover(i)}
55
+ onMouseLeave={() => setHover((p) => (p === i ? null : p))}
56
+ style={{
57
+ position: "relative",
58
+ marginLeft: i === 0 ? 0 : -overlap,
59
+ zIndex: h ? 50 : shown.length - i,
60
+ transform: h ? `translateY(-${lift}px) scale(1.2)` : "none",
61
+ transition: "transform 160ms cubic-bezier(0.23, 1, 0.32, 1)",
62
+ display: "inline-flex",
63
+ }}
64
+ >
65
+ {/* Ticker tooltip — sits just above the circle. */}
66
+ {h && <span style={tooltipStyle}>{a.label}</span>}
67
+ <span
68
+ style={{
69
+ width: size,
70
+ height: size,
71
+ borderRadius: "9999px",
72
+ overflow: "hidden",
73
+ display: "inline-flex",
74
+ alignItems: "center",
75
+ justifyContent: "center",
76
+ background: "#fff",
77
+ boxShadow: "0 0 0 2px #fff",
78
+ }}
79
+ >
80
+ {/* eslint-disable-next-line @next/next/no-img-element */}
81
+ <img
82
+ src={a.iconUrl}
83
+ alt={a.label}
84
+ width={size}
85
+ height={size}
86
+ style={{ width: size, height: size, objectFit: "cover", display: "block" }}
87
+ />
88
+ </span>
89
+ </span>
90
+ );
91
+ })}
92
+
93
+ {overflow > 0 && (
94
+ <span
95
+ style={{
96
+ width: size,
97
+ height: size,
98
+ marginLeft: -overlap,
99
+ borderRadius: "9999px",
100
+ display: "grid",
101
+ placeItems: "center",
102
+ background: "#F4F4F4",
103
+ boxShadow: "0 0 0 2px #fff",
104
+ fontSize: Math.max(9, Math.round(size * 0.5)),
105
+ fontWeight: 600,
106
+ color: "#6a55cf",
107
+ }}
108
+ >
109
+ +{overflow}
110
+ </span>
111
+ )}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ const tooltipStyle: CSSProperties = {
117
+ position: "absolute",
118
+ bottom: "100%",
119
+ left: "50%",
120
+ transform: "translateX(-50%)",
121
+ marginBottom: 6,
122
+ whiteSpace: "nowrap",
123
+ padding: "2px 6px",
124
+ borderRadius: 6,
125
+ fontSize: 9,
126
+ fontWeight: 700,
127
+ background: "#6a55cf",
128
+ color: "#fff",
129
+ pointerEvents: "none",
130
+ zIndex: 60,
131
+ boxShadow: "0 2px 6px -2px rgba(0,0,0,0.3)",
132
+ };
133
+
134
+ export default AssetCircles;
@@ -11,3 +11,8 @@ export {
11
11
  type WalletDropdownProps,
12
12
  type WalletDropdownClassNames,
13
13
  } from "./wallet-dropdown";
14
+ export {
15
+ AssetCircles,
16
+ type AssetCircleItem,
17
+ type AssetCirclesProps,
18
+ } from "./avatar-circles";
@@ -21,6 +21,7 @@ export interface WalletDropdownClassNames {
21
21
  tabActive?: string;
22
22
  accountRow?: string;
23
23
  balanceRow?: string;
24
+ signOut?: string;
24
25
  }
25
26
 
26
27
  export interface WalletDropdownProps {
@@ -58,6 +59,14 @@ export interface WalletDropdownProps {
58
59
  /** Shown when authed but no accounts yet (provisioning). */
59
60
  emptyState?: ReactNode;
60
61
  onSelectAccount?: (account: WalletAccount) => void;
62
+ /**
63
+ * When provided, a "Log out" button renders at the BOTTOM of the open panel
64
+ * (below children). Both apps wire their own session sign-out — desk via
65
+ * Supabase, defi-web via the BUFI session — so the control lives in one place.
66
+ */
67
+ onSignOut?: () => void;
68
+ /** Label for the sign-out button (default "Log out"). */
69
+ signOutLabel?: string;
61
70
  classNames?: WalletDropdownClassNames;
62
71
  }
63
72
 
@@ -79,6 +88,8 @@ export function WalletDropdown(props: WalletDropdownProps): ReactNode {
79
88
  renderBalances = true,
80
89
  emptyState,
81
90
  onSelectAccount,
91
+ onSignOut,
92
+ signOutLabel = "Log out",
82
93
  classNames = {},
83
94
  } = props;
84
95
 
@@ -188,6 +199,18 @@ export function WalletDropdown(props: WalletDropdownProps): ReactNode {
188
199
  {children}
189
200
  </>
190
201
  )}
202
+
203
+ {/* Sign out — always the last thing in the panel. */}
204
+ {onSignOut && (
205
+ <button
206
+ type="button"
207
+ className={classNames.signOut}
208
+ onClick={onSignOut}
209
+ data-bufi-signout
210
+ >
211
+ {signOutLabel}
212
+ </button>
213
+ )}
191
214
  </div>
192
215
  )}
193
216
  </div>