kaleido-ui 0.1.72 → 0.1.74

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.
@@ -1156,7 +1156,8 @@ function dispatch(action) {
1156
1156
  memoryState = reducer(memoryState, action);
1157
1157
  listeners.forEach((listener) => listener(memoryState));
1158
1158
  }
1159
- function toast({ duration = 4e3, ...props }) {
1159
+ function toast({ duration, ...props }) {
1160
+ const effectiveDuration = duration ?? (props.variant === "destructive" ? 12e3 : 4e3);
1160
1161
  const id = genId();
1161
1162
  const update = (props2) => dispatch({ type: "UPDATE_TOAST", toast: { ...props2, id } });
1162
1163
  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
@@ -1166,14 +1167,14 @@ function toast({ duration = 4e3, ...props }) {
1166
1167
  ...props,
1167
1168
  id,
1168
1169
  open: true,
1169
- duration,
1170
+ duration: effectiveDuration,
1170
1171
  onOpenChange: (open) => {
1171
1172
  if (!open) dismiss();
1172
1173
  }
1173
1174
  }
1174
1175
  });
1175
- if (duration > 0) {
1176
- setTimeout(() => dismiss(), duration);
1176
+ if (effectiveDuration > 0) {
1177
+ setTimeout(() => dismiss(), effectiveDuration);
1177
1178
  }
1178
1179
  return { id, dismiss, update };
1179
1180
  }
@@ -1195,8 +1196,16 @@ function useToast() {
1195
1196
 
1196
1197
  // src/web/primitives/toaster.tsx
1197
1198
  var import_jsx_runtime14 = require("react/jsx-runtime");
1199
+ function toPlainText(node) {
1200
+ if (node == null || node === false) return "";
1201
+ if (typeof node === "string" || typeof node === "number") return String(node);
1202
+ if (Array.isArray(node)) return node.map(toPlainText).join("");
1203
+ const kids = node?.props?.children;
1204
+ return kids != null ? toPlainText(kids) : "";
1205
+ }
1198
1206
  function ToastWithProgress({ id, title, description, action, duration = 4e3, variant, ...props }) {
1199
1207
  const [progress, setProgress] = (0, import_react.useState)(100);
1208
+ const [copied, setCopied] = (0, import_react.useState)(false);
1200
1209
  (0, import_react.useEffect)(() => {
1201
1210
  const interval = 50;
1202
1211
  const decrement = interval / duration * 100;
@@ -1214,16 +1223,36 @@ function ToastWithProgress({ id, title, description, action, duration = 4e3, var
1214
1223
  }
1215
1224
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "check_circle", size: "md", className: "text-primary" });
1216
1225
  };
1226
+ const showCopy = variant === "destructive";
1227
+ const copyText = () => {
1228
+ const text = [toPlainText(title), toPlainText(description)].filter(Boolean).join("\n");
1229
+ if (!text) return;
1230
+ void Promise.resolve(navigator?.clipboard?.writeText?.(text)).catch(() => {
1231
+ });
1232
+ setCopied(true);
1233
+ setTimeout(() => setCopied(false), 1500);
1234
+ };
1217
1235
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Toast, { ...props, variant, children: [
1218
1236
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-start gap-3 flex-1", children: [
1219
1237
  getIcon(),
1220
1238
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "grid gap-1 flex-1", children: [
1221
1239
  title && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToastTitle, { children: title }),
1222
- description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToastDescription, { children: description })
1240
+ description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToastDescription, { className: "select-text", children: description })
1223
1241
  ] })
1224
1242
  ] }),
1225
1243
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
1226
1244
  action,
1245
+ showCopy && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1246
+ "button",
1247
+ {
1248
+ type: "button",
1249
+ onClick: copyText,
1250
+ "aria-label": copied ? "Copied" : "Copy error",
1251
+ title: copied ? "Copied" : "Copy error",
1252
+ className: "rounded-md p-1 text-foreground/60 hover:text-foreground hover:bg-white/10 transition-colors",
1253
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: copied ? "check" : "content_copy", size: "sm" })
1254
+ }
1255
+ ),
1227
1256
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToastClose, {})
1228
1257
  ] }),
1229
1258
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-white/10 overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
@@ -2245,7 +2274,7 @@ var LOGO_PATHS = /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runti
2245
2274
  ),
2246
2275
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("path", { d: "M274.479 0.104797H411.786L274.533 137.411H137.226L274.479 0.104797Z", fill: "#17B581" })
2247
2276
  ] });
2248
- function QrCode({ value, size = 160, className }) {
2277
+ function QrCode({ value, size = 160, className, tone = "onLight" }) {
2249
2278
  const svgContent = (0, import_react6.useMemo)(() => {
2250
2279
  if (!value) return null;
2251
2280
  const matrix = (0, import_qr.default)(value, "raw", { ecc: "medium", border: 0 });
@@ -2253,8 +2282,8 @@ function QrCode({ value, size = 160, className }) {
2253
2282
  const moduleSize = 10;
2254
2283
  const quietZone = moduleSize * 2;
2255
2284
  const svgSize = n * moduleSize + quietZone * 2;
2256
- const fg = "#040404";
2257
- const bg = "#ffffff";
2285
+ const fg = tone === "onDark" ? "#ffffff" : "#040404";
2286
+ const bg = tone === "onDark" ? "transparent" : "#ffffff";
2258
2287
  const logoModules = Math.ceil(n * 0.2);
2259
2288
  const logoZoneSize = logoModules % 2 === 0 ? logoModules + 1 : logoModules;
2260
2289
  const elements = [];
@@ -2308,7 +2337,7 @@ function QrCode({ value, size = 160, className }) {
2308
2337
  children: elements
2309
2338
  }
2310
2339
  );
2311
- }, [value]);
2340
+ }, [value, tone]);
2312
2341
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className, style: { width: size, height: size }, children: svgContent });
2313
2342
  }
2314
2343
 
@@ -7179,7 +7208,7 @@ var NETWORK_CONFIG = {
7179
7208
  border: "border-network-bitcoin/40",
7180
7209
  qrBorder: "border-network-bitcoin/30",
7181
7210
  qrGlow: qrGlowStyle(colors.network.bitcoin),
7182
- icon: /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("span", { className: "material-symbols-outlined text-icon-xs leading-none", children: "link" })
7211
+ icon: /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("span", { className: "material-symbols-outlined text-icon-xs leading-none", children: "currency_bitcoin" })
7183
7212
  },
7184
7213
  lightning: {
7185
7214
  label: "Lightning",
@@ -7591,13 +7620,13 @@ function DepositNetworkDefaultModal({
7591
7620
  }
7592
7621
 
7593
7622
  // src/web/components/btc-unified-receive.tsx
7623
+ var import_react16 = require("react");
7594
7624
  var import_jsx_runtime76 = require("react/jsx-runtime");
7595
7625
  function formatSatsForRow(value) {
7596
7626
  if (!value || value <= 0 || !Number.isFinite(value)) return null;
7597
7627
  return `${value.toLocaleString("en-US")} sats`;
7598
7628
  }
7599
7629
  function BtcUnifiedReceive({
7600
- btcSelectedAccount,
7601
7630
  accountReceiveResult,
7602
7631
  invoiceStatus,
7603
7632
  isInvoicePending,
@@ -7612,66 +7641,56 @@ function BtcUnifiedReceive({
7612
7641
  setAmount,
7613
7642
  setInvoiceStatus,
7614
7643
  setAccountReceiveResult,
7615
- handleDone,
7616
- showRegenerate = true
7644
+ showRegenerate = true,
7645
+ description,
7646
+ onDescriptionChange
7617
7647
  }) {
7618
- const accountNetwork = btcSelectedAccount === "SPARK" ? "spark" : btcSelectedAccount === "ARKADE" ? "arkade" : "onchain";
7619
- const qrNetwork = NETWORK_CONFIG[accountNetwork];
7648
+ const [showEdit, setShowEdit] = (0, import_react16.useState)(false);
7649
+ const isQrCopied = copied === accountReceiveResult.qrValue;
7650
+ const handleNewAddress = () => {
7651
+ setAddress("");
7652
+ setAmount("");
7653
+ setInvoiceStatus(null);
7654
+ setAccountReceiveResult(null);
7655
+ };
7620
7656
  return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3 animate-in fade-in zoom-in-95 duration-300", children: [
7621
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex flex-col gap-1.5 rounded-xl border border-white/8 bg-white/3 p-2.5", children: [
7622
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "flex items-center justify-between px-1", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Amount (optional)" }) }),
7623
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7624
- "input",
7625
- {
7626
- type: "text",
7627
- value: amount,
7628
- onChange: handleAmountChange,
7629
- placeholder: "Any amount",
7630
- className: "w-full rounded-lg border bg-white/5 px-3 py-1.5 font-mono text-xs font-bold text-white transition-all placeholder:text-white/25 focus:border-primary/40 focus:outline-none",
7631
- inputMode: "decimal"
7632
- }
7633
- ),
7634
- amount && loading && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "flex items-center gap-1 text-xxs text-warning/70", children: [
7635
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { className: "material-symbols-outlined animate-spin text-icon-xxs", children: "progress_activity" }),
7636
- "Updating invoice..."
7637
- ] })
7638
- ] }),
7639
7657
  /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex flex-col items-center gap-3", children: [
7640
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(
7641
- "div",
7642
- {
7643
- className: cn(
7644
- "relative flex flex-col items-center rounded-2xl border-2 bg-white p-2 transition-all",
7645
- qrNetwork.qrBorder
7646
- ),
7647
- style: qrNetwork.qrGlow,
7648
- children: [
7649
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(QrCode, { value: accountReceiveResult.qrValue, size: 200 }),
7650
- isInvoicePaid && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PaidOverlay, {})
7651
- ]
7652
- }
7653
- ),
7654
- (() => {
7655
- const isQrCopied = copied === accountReceiveResult.qrValue;
7656
- return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(
7657
- "button",
7658
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "relative flex flex-col items-center p-2", children: [
7659
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(QrCode, { value: accountReceiveResult.qrValue, size: 200, tone: "onDark" }),
7660
+ isInvoicePaid && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PaidOverlay, {})
7661
+ ] }),
7662
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-center justify-center gap-2.5", children: [
7663
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7664
+ Button,
7658
7665
  {
7659
- type: "button",
7660
- className: cn(
7661
- "flex items-center gap-1 rounded-full border px-2.5 py-1 text-xxs font-bold uppercase tracking-widest transition-all",
7662
- isQrCopied ? "border-primary/30 bg-primary/10 text-primary" : "border-border bg-white/5 text-muted-foreground hover:border-white/20 hover:bg-accent hover:text-white"
7663
- ),
7664
- onClick: (event) => {
7665
- event.stopPropagation();
7666
- void copyToClipboard(accountReceiveResult.qrValue);
7667
- },
7668
- children: [
7669
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Icon, { name: isQrCopied ? "check" : "content_copy", size: "xs" }),
7670
- isQrCopied ? "Copied" : `Copy ${accountReceiveResult.qrLabel}`
7671
- ]
7666
+ variant: "surface",
7667
+ size: "icon-xl",
7668
+ "aria-label": `Copy ${accountReceiveResult.qrLabel}`,
7669
+ onClick: () => void copyToClipboard(accountReceiveResult.qrValue),
7670
+ children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Icon, { name: isQrCopied ? "check" : "content_copy", size: "lg" })
7672
7671
  }
7673
- );
7674
- })()
7672
+ ),
7673
+ showRegenerate && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7674
+ Button,
7675
+ {
7676
+ variant: "surface",
7677
+ size: "icon-xl",
7678
+ "aria-label": "New address",
7679
+ onClick: handleNewAddress,
7680
+ children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Icon, { name: "refresh", size: "lg" })
7681
+ }
7682
+ ),
7683
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7684
+ Button,
7685
+ {
7686
+ variant: "surface",
7687
+ size: "icon-xl",
7688
+ "aria-label": "Edit amount and description",
7689
+ onClick: () => setShowEdit(true),
7690
+ children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Icon, { name: "edit", size: "lg" })
7691
+ }
7692
+ )
7693
+ ] })
7675
7694
  ] }),
7676
7695
  invoiceStatus && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7677
7696
  InvoiceStatusBanner,
@@ -7690,9 +7709,8 @@ function BtcUnifiedReceive({
7690
7709
  "div",
7691
7710
  {
7692
7711
  className: cn(
7693
- "group flex cursor-pointer items-center gap-2 rounded-xl border bg-white/3 px-2.5 py-1.5",
7694
- "transition-all hover:bg-white/6 active:scale-[0.98]",
7695
- network.border
7712
+ "group flex cursor-pointer items-center gap-2 rounded-xl bg-white/3 px-2.5 py-1.5",
7713
+ "transition-all hover:bg-white/6 active:scale-[0.98]"
7696
7714
  ),
7697
7715
  style: { borderLeftWidth: 3, borderLeftColor: network.color },
7698
7716
  onClick: () => void copyToClipboard(address.value),
@@ -7716,36 +7734,48 @@ function BtcUnifiedReceive({
7716
7734
  })
7717
7735
  ] }),
7718
7736
  /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7719
- NetworkInfoDisclosure,
7737
+ BottomSheet,
7720
7738
  {
7721
- networks: Array.from(
7722
- new Set(accountReceiveResult.addresses.map((address) => address.network))
7723
- )
7739
+ open: showEdit,
7740
+ title: "Edit request",
7741
+ icon: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Icon, { name: "edit", size: "md" }),
7742
+ onClose: () => setShowEdit(false),
7743
+ children: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-4", children: [
7744
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-1.5", children: [
7745
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Amount (optional)" }),
7746
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7747
+ "input",
7748
+ {
7749
+ type: "text",
7750
+ value: amount,
7751
+ onChange: handleAmountChange,
7752
+ placeholder: "Any amount",
7753
+ className: "w-full rounded-xl bg-white/5 px-3 py-2.5 font-mono text-sm font-bold text-white shadow-inner transition-all placeholder:text-white/25 focus:outline focus:outline-2 focus:outline-primary/40",
7754
+ inputMode: "decimal"
7755
+ }
7756
+ ),
7757
+ amount && loading && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "flex items-center gap-1 text-xxs text-warning/70", children: [
7758
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { className: "material-symbols-outlined animate-spin text-icon-xxs", children: "progress_activity" }),
7759
+ "Updating invoice..."
7760
+ ] })
7761
+ ] }),
7762
+ onDescriptionChange && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-1.5", children: [
7763
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Description (optional)" }),
7764
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
7765
+ "input",
7766
+ {
7767
+ type: "text",
7768
+ value: description ?? "",
7769
+ onChange: (event) => onDescriptionChange(event.target.value),
7770
+ placeholder: "What's this for?",
7771
+ className: "w-full rounded-xl bg-white/5 px-3 py-2.5 text-sm text-white shadow-inner transition-all placeholder:text-white/25 focus:outline focus:outline-2 focus:outline-primary/40"
7772
+ }
7773
+ )
7774
+ ] }),
7775
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Button, { variant: "cta", className: "w-full", onClick: () => setShowEdit(false), children: "Done" })
7776
+ ] })
7724
7777
  }
7725
- ),
7726
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex gap-2.5 pt-1", children: [
7727
- showRegenerate && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(
7728
- "button",
7729
- {
7730
- type: "button",
7731
- onClick: () => {
7732
- setAddress("");
7733
- setAmount("");
7734
- setInvoiceStatus(null);
7735
- setAccountReceiveResult(null);
7736
- },
7737
- className: "flex flex-1 items-center justify-center gap-1.5 rounded-xl border py-3 text-xs font-bold text-muted-foreground transition-all hover:border-border hover:bg-accent hover:text-white active:scale-[0.98]",
7738
- children: [
7739
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { className: "material-symbols-outlined text-icon-sm", children: "refresh" }),
7740
- "New Address"
7741
- ]
7742
- }
7743
- ),
7744
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(Button, { variant: "cta", onClick: handleDone, className: showRegenerate ? void 0 : "flex-1", children: [
7745
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { className: "material-symbols-outlined text-icon-sm", children: "check" }),
7746
- "Done"
7747
- ] })
7748
- ] })
7778
+ )
7749
7779
  ] });
7750
7780
  }
7751
7781
 
@@ -8167,7 +8197,7 @@ function DepositPreGeneration({
8167
8197
  }
8168
8198
 
8169
8199
  // src/web/components/deposit-asset-selection.tsx
8170
- var import_react16 = require("react");
8200
+ var import_react17 = require("react");
8171
8201
  var import_jsx_runtime79 = require("react/jsx-runtime");
8172
8202
  var ADD_ASSET_SUBTITLE = {
8173
8203
  RGB: "RGB asset on Bitcoin",
@@ -8210,9 +8240,9 @@ function DepositAssetSelection({
8210
8240
  const ownedAssetsCount = ownedAssets.length;
8211
8241
  const noResults = filteredAssets.length === 0 && !!searchQuery;
8212
8242
  const isSearching = !!searchQuery;
8213
- const [assetsExpanded, setAssetsExpanded] = (0, import_react16.useState)(false);
8214
- const [showAddAssetModal, setShowAddAssetModal] = (0, import_react16.useState)(false);
8215
- (0, import_react16.useEffect)(() => {
8243
+ const [assetsExpanded, setAssetsExpanded] = (0, import_react17.useState)(false);
8244
+ const [showAddAssetModal, setShowAddAssetModal] = (0, import_react17.useState)(false);
8245
+ (0, import_react17.useEffect)(() => {
8216
8246
  if (isSearching) setAssetsExpanded(true);
8217
8247
  }, [isSearching]);
8218
8248
  const showOwnedAssets = isSearching ? true : assetsExpanded;
@@ -334,8 +334,14 @@ interface QrCodeProps {
334
334
  value: string;
335
335
  size?: number;
336
336
  className?: string;
337
+ /**
338
+ * 'onLight' (default) renders dark modules on a white card — use inside a
339
+ * white container. 'onDark' renders white modules on a transparent
340
+ * background so the QR sits directly on the app background.
341
+ */
342
+ tone?: 'onLight' | 'onDark';
337
343
  }
338
- declare function QrCode({ value, size, className }: QrCodeProps): react_jsx_runtime.JSX.Element;
344
+ declare function QrCode({ value, size, className, tone }: QrCodeProps): react_jsx_runtime.JSX.Element;
339
345
 
340
346
  interface BottomNavItem<TValue extends string = string> {
341
347
  id: TValue;
@@ -1468,8 +1474,12 @@ interface BtcUnifiedReceiveProps {
1468
1474
  * button leads to a half-initialised state).
1469
1475
  */
1470
1476
  showRegenerate?: boolean;
1477
+ /** Optional request description/memo, edited alongside the amount in the
1478
+ * Edit modal. When omitted the description field is hidden. */
1479
+ description?: string;
1480
+ onDescriptionChange?: (value: string) => void;
1471
1481
  }
1472
- declare function BtcUnifiedReceive({ btcSelectedAccount, accountReceiveResult, invoiceStatus, isInvoicePending, isInvoicePaid, isInvoiceFailedOrExpired, amount, handleAmountChange, loading, copied, copyToClipboard, setAddress, setAmount, setInvoiceStatus, setAccountReceiveResult, handleDone, showRegenerate, }: BtcUnifiedReceiveProps): react_jsx_runtime.JSX.Element;
1482
+ declare function BtcUnifiedReceive({ accountReceiveResult, invoiceStatus, isInvoicePending, isInvoicePaid, isInvoiceFailedOrExpired, amount, handleAmountChange, loading, copied, copyToClipboard, setAddress, setAmount, setInvoiceStatus, setAccountReceiveResult, showRegenerate, description, onDescriptionChange, }: BtcUnifiedReceiveProps): react_jsx_runtime.JSX.Element;
1473
1483
 
1474
1484
  interface DepositGeneratedAsset {
1475
1485
  ticker?: string;
@@ -334,8 +334,14 @@ interface QrCodeProps {
334
334
  value: string;
335
335
  size?: number;
336
336
  className?: string;
337
+ /**
338
+ * 'onLight' (default) renders dark modules on a white card — use inside a
339
+ * white container. 'onDark' renders white modules on a transparent
340
+ * background so the QR sits directly on the app background.
341
+ */
342
+ tone?: 'onLight' | 'onDark';
337
343
  }
338
- declare function QrCode({ value, size, className }: QrCodeProps): react_jsx_runtime.JSX.Element;
344
+ declare function QrCode({ value, size, className, tone }: QrCodeProps): react_jsx_runtime.JSX.Element;
339
345
 
340
346
  interface BottomNavItem<TValue extends string = string> {
341
347
  id: TValue;
@@ -1468,8 +1474,12 @@ interface BtcUnifiedReceiveProps {
1468
1474
  * button leads to a half-initialised state).
1469
1475
  */
1470
1476
  showRegenerate?: boolean;
1477
+ /** Optional request description/memo, edited alongside the amount in the
1478
+ * Edit modal. When omitted the description field is hidden. */
1479
+ description?: string;
1480
+ onDescriptionChange?: (value: string) => void;
1471
1481
  }
1472
- declare function BtcUnifiedReceive({ btcSelectedAccount, accountReceiveResult, invoiceStatus, isInvoicePending, isInvoicePaid, isInvoiceFailedOrExpired, amount, handleAmountChange, loading, copied, copyToClipboard, setAddress, setAmount, setInvoiceStatus, setAccountReceiveResult, handleDone, showRegenerate, }: BtcUnifiedReceiveProps): react_jsx_runtime.JSX.Element;
1482
+ declare function BtcUnifiedReceive({ accountReceiveResult, invoiceStatus, isInvoicePending, isInvoicePaid, isInvoiceFailedOrExpired, amount, handleAmountChange, loading, copied, copyToClipboard, setAddress, setAmount, setInvoiceStatus, setAccountReceiveResult, showRegenerate, description, onDescriptionChange, }: BtcUnifiedReceiveProps): react_jsx_runtime.JSX.Element;
1473
1483
 
1474
1484
  interface DepositGeneratedAsset {
1475
1485
  ticker?: string;
package/dist/web/index.js CHANGED
@@ -972,7 +972,8 @@ function dispatch(action) {
972
972
  memoryState = reducer(memoryState, action);
973
973
  listeners.forEach((listener) => listener(memoryState));
974
974
  }
975
- function toast({ duration = 4e3, ...props }) {
975
+ function toast({ duration, ...props }) {
976
+ const effectiveDuration = duration ?? (props.variant === "destructive" ? 12e3 : 4e3);
976
977
  const id = genId();
977
978
  const update = (props2) => dispatch({ type: "UPDATE_TOAST", toast: { ...props2, id } });
978
979
  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
@@ -982,14 +983,14 @@ function toast({ duration = 4e3, ...props }) {
982
983
  ...props,
983
984
  id,
984
985
  open: true,
985
- duration,
986
+ duration: effectiveDuration,
986
987
  onOpenChange: (open) => {
987
988
  if (!open) dismiss();
988
989
  }
989
990
  }
990
991
  });
991
- if (duration > 0) {
992
- setTimeout(() => dismiss(), duration);
992
+ if (effectiveDuration > 0) {
993
+ setTimeout(() => dismiss(), effectiveDuration);
993
994
  }
994
995
  return { id, dismiss, update };
995
996
  }
@@ -1011,8 +1012,16 @@ function useToast() {
1011
1012
 
1012
1013
  // src/web/primitives/toaster.tsx
1013
1014
  import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
1015
+ function toPlainText(node) {
1016
+ if (node == null || node === false) return "";
1017
+ if (typeof node === "string" || typeof node === "number") return String(node);
1018
+ if (Array.isArray(node)) return node.map(toPlainText).join("");
1019
+ const kids = node?.props?.children;
1020
+ return kids != null ? toPlainText(kids) : "";
1021
+ }
1014
1022
  function ToastWithProgress({ id, title, description, action, duration = 4e3, variant, ...props }) {
1015
1023
  const [progress, setProgress] = useState2(100);
1024
+ const [copied, setCopied] = useState2(false);
1016
1025
  useEffect2(() => {
1017
1026
  const interval = 50;
1018
1027
  const decrement = interval / duration * 100;
@@ -1030,16 +1039,36 @@ function ToastWithProgress({ id, title, description, action, duration = 4e3, var
1030
1039
  }
1031
1040
  return /* @__PURE__ */ jsx14(Icon, { name: "check_circle", size: "md", className: "text-primary" });
1032
1041
  };
1042
+ const showCopy = variant === "destructive";
1043
+ const copyText = () => {
1044
+ const text = [toPlainText(title), toPlainText(description)].filter(Boolean).join("\n");
1045
+ if (!text) return;
1046
+ void Promise.resolve(navigator?.clipboard?.writeText?.(text)).catch(() => {
1047
+ });
1048
+ setCopied(true);
1049
+ setTimeout(() => setCopied(false), 1500);
1050
+ };
1033
1051
  return /* @__PURE__ */ jsxs5(Toast, { ...props, variant, children: [
1034
1052
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-3 flex-1", children: [
1035
1053
  getIcon(),
1036
1054
  /* @__PURE__ */ jsxs5("div", { className: "grid gap-1 flex-1", children: [
1037
1055
  title && /* @__PURE__ */ jsx14(ToastTitle, { children: title }),
1038
- description && /* @__PURE__ */ jsx14(ToastDescription, { children: description })
1056
+ description && /* @__PURE__ */ jsx14(ToastDescription, { className: "select-text", children: description })
1039
1057
  ] })
1040
1058
  ] }),
1041
1059
  /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 shrink-0", children: [
1042
1060
  action,
1061
+ showCopy && /* @__PURE__ */ jsx14(
1062
+ "button",
1063
+ {
1064
+ type: "button",
1065
+ onClick: copyText,
1066
+ "aria-label": copied ? "Copied" : "Copy error",
1067
+ title: copied ? "Copied" : "Copy error",
1068
+ className: "rounded-md p-1 text-foreground/60 hover:text-foreground hover:bg-white/10 transition-colors",
1069
+ children: /* @__PURE__ */ jsx14(Icon, { name: copied ? "check" : "content_copy", size: "sm" })
1070
+ }
1071
+ ),
1043
1072
  /* @__PURE__ */ jsx14(ToastClose, {})
1044
1073
  ] }),
1045
1074
  /* @__PURE__ */ jsx14("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-white/10 overflow-hidden", children: /* @__PURE__ */ jsx14(
@@ -2067,7 +2096,7 @@ var LOGO_PATHS = /* @__PURE__ */ jsxs13(Fragment, { children: [
2067
2096
  ),
2068
2097
  /* @__PURE__ */ jsx26("path", { d: "M274.479 0.104797H411.786L274.533 137.411H137.226L274.479 0.104797Z", fill: "#17B581" })
2069
2098
  ] });
2070
- function QrCode({ value, size = 160, className }) {
2099
+ function QrCode({ value, size = 160, className, tone = "onLight" }) {
2071
2100
  const svgContent = useMemo(() => {
2072
2101
  if (!value) return null;
2073
2102
  const matrix = encodeQR(value, "raw", { ecc: "medium", border: 0 });
@@ -2075,8 +2104,8 @@ function QrCode({ value, size = 160, className }) {
2075
2104
  const moduleSize = 10;
2076
2105
  const quietZone = moduleSize * 2;
2077
2106
  const svgSize = n * moduleSize + quietZone * 2;
2078
- const fg = "#040404";
2079
- const bg = "#ffffff";
2107
+ const fg = tone === "onDark" ? "#ffffff" : "#040404";
2108
+ const bg = tone === "onDark" ? "transparent" : "#ffffff";
2080
2109
  const logoModules = Math.ceil(n * 0.2);
2081
2110
  const logoZoneSize = logoModules % 2 === 0 ? logoModules + 1 : logoModules;
2082
2111
  const elements = [];
@@ -2130,7 +2159,7 @@ function QrCode({ value, size = 160, className }) {
2130
2159
  children: elements
2131
2160
  }
2132
2161
  );
2133
- }, [value]);
2162
+ }, [value, tone]);
2134
2163
  return /* @__PURE__ */ jsx26("div", { className, style: { width: size, height: size }, children: svgContent });
2135
2164
  }
2136
2165
 
@@ -7001,7 +7030,7 @@ var NETWORK_CONFIG = {
7001
7030
  border: "border-network-bitcoin/40",
7002
7031
  qrBorder: "border-network-bitcoin/30",
7003
7032
  qrGlow: qrGlowStyle(colors.network.bitcoin),
7004
- icon: /* @__PURE__ */ jsx73("span", { className: "material-symbols-outlined text-icon-xs leading-none", children: "link" })
7033
+ icon: /* @__PURE__ */ jsx73("span", { className: "material-symbols-outlined text-icon-xs leading-none", children: "currency_bitcoin" })
7005
7034
  },
7006
7035
  lightning: {
7007
7036
  label: "Lightning",
@@ -7413,13 +7442,13 @@ function DepositNetworkDefaultModal({
7413
7442
  }
7414
7443
 
7415
7444
  // src/web/components/btc-unified-receive.tsx
7445
+ import { useState as useState15 } from "react";
7416
7446
  import { jsx as jsx76, jsxs as jsxs62 } from "react/jsx-runtime";
7417
7447
  function formatSatsForRow(value) {
7418
7448
  if (!value || value <= 0 || !Number.isFinite(value)) return null;
7419
7449
  return `${value.toLocaleString("en-US")} sats`;
7420
7450
  }
7421
7451
  function BtcUnifiedReceive({
7422
- btcSelectedAccount,
7423
7452
  accountReceiveResult,
7424
7453
  invoiceStatus,
7425
7454
  isInvoicePending,
@@ -7434,66 +7463,56 @@ function BtcUnifiedReceive({
7434
7463
  setAmount,
7435
7464
  setInvoiceStatus,
7436
7465
  setAccountReceiveResult,
7437
- handleDone,
7438
- showRegenerate = true
7466
+ showRegenerate = true,
7467
+ description,
7468
+ onDescriptionChange
7439
7469
  }) {
7440
- const accountNetwork = btcSelectedAccount === "SPARK" ? "spark" : btcSelectedAccount === "ARKADE" ? "arkade" : "onchain";
7441
- const qrNetwork = NETWORK_CONFIG[accountNetwork];
7470
+ const [showEdit, setShowEdit] = useState15(false);
7471
+ const isQrCopied = copied === accountReceiveResult.qrValue;
7472
+ const handleNewAddress = () => {
7473
+ setAddress("");
7474
+ setAmount("");
7475
+ setInvoiceStatus(null);
7476
+ setAccountReceiveResult(null);
7477
+ };
7442
7478
  return /* @__PURE__ */ jsxs62("div", { className: "space-y-3 animate-in fade-in zoom-in-95 duration-300", children: [
7443
- /* @__PURE__ */ jsxs62("div", { className: "flex flex-col gap-1.5 rounded-xl border border-white/8 bg-white/3 p-2.5", children: [
7444
- /* @__PURE__ */ jsx76("div", { className: "flex items-center justify-between px-1", children: /* @__PURE__ */ jsx76("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Amount (optional)" }) }),
7445
- /* @__PURE__ */ jsx76(
7446
- "input",
7447
- {
7448
- type: "text",
7449
- value: amount,
7450
- onChange: handleAmountChange,
7451
- placeholder: "Any amount",
7452
- className: "w-full rounded-lg border bg-white/5 px-3 py-1.5 font-mono text-xs font-bold text-white transition-all placeholder:text-white/25 focus:border-primary/40 focus:outline-none",
7453
- inputMode: "decimal"
7454
- }
7455
- ),
7456
- amount && loading && /* @__PURE__ */ jsxs62("p", { className: "flex items-center gap-1 text-xxs text-warning/70", children: [
7457
- /* @__PURE__ */ jsx76("span", { className: "material-symbols-outlined animate-spin text-icon-xxs", children: "progress_activity" }),
7458
- "Updating invoice..."
7459
- ] })
7460
- ] }),
7461
7479
  /* @__PURE__ */ jsxs62("div", { className: "flex flex-col items-center gap-3", children: [
7462
- /* @__PURE__ */ jsxs62(
7463
- "div",
7464
- {
7465
- className: cn(
7466
- "relative flex flex-col items-center rounded-2xl border-2 bg-white p-2 transition-all",
7467
- qrNetwork.qrBorder
7468
- ),
7469
- style: qrNetwork.qrGlow,
7470
- children: [
7471
- /* @__PURE__ */ jsx76(QrCode, { value: accountReceiveResult.qrValue, size: 200 }),
7472
- isInvoicePaid && /* @__PURE__ */ jsx76(PaidOverlay, {})
7473
- ]
7474
- }
7475
- ),
7476
- (() => {
7477
- const isQrCopied = copied === accountReceiveResult.qrValue;
7478
- return /* @__PURE__ */ jsxs62(
7479
- "button",
7480
+ /* @__PURE__ */ jsxs62("div", { className: "relative flex flex-col items-center p-2", children: [
7481
+ /* @__PURE__ */ jsx76(QrCode, { value: accountReceiveResult.qrValue, size: 200, tone: "onDark" }),
7482
+ isInvoicePaid && /* @__PURE__ */ jsx76(PaidOverlay, {})
7483
+ ] }),
7484
+ /* @__PURE__ */ jsxs62("div", { className: "flex items-center justify-center gap-2.5", children: [
7485
+ /* @__PURE__ */ jsx76(
7486
+ Button,
7480
7487
  {
7481
- type: "button",
7482
- className: cn(
7483
- "flex items-center gap-1 rounded-full border px-2.5 py-1 text-xxs font-bold uppercase tracking-widest transition-all",
7484
- isQrCopied ? "border-primary/30 bg-primary/10 text-primary" : "border-border bg-white/5 text-muted-foreground hover:border-white/20 hover:bg-accent hover:text-white"
7485
- ),
7486
- onClick: (event) => {
7487
- event.stopPropagation();
7488
- void copyToClipboard(accountReceiveResult.qrValue);
7489
- },
7490
- children: [
7491
- /* @__PURE__ */ jsx76(Icon, { name: isQrCopied ? "check" : "content_copy", size: "xs" }),
7492
- isQrCopied ? "Copied" : `Copy ${accountReceiveResult.qrLabel}`
7493
- ]
7488
+ variant: "surface",
7489
+ size: "icon-xl",
7490
+ "aria-label": `Copy ${accountReceiveResult.qrLabel}`,
7491
+ onClick: () => void copyToClipboard(accountReceiveResult.qrValue),
7492
+ children: /* @__PURE__ */ jsx76(Icon, { name: isQrCopied ? "check" : "content_copy", size: "lg" })
7494
7493
  }
7495
- );
7496
- })()
7494
+ ),
7495
+ showRegenerate && /* @__PURE__ */ jsx76(
7496
+ Button,
7497
+ {
7498
+ variant: "surface",
7499
+ size: "icon-xl",
7500
+ "aria-label": "New address",
7501
+ onClick: handleNewAddress,
7502
+ children: /* @__PURE__ */ jsx76(Icon, { name: "refresh", size: "lg" })
7503
+ }
7504
+ ),
7505
+ /* @__PURE__ */ jsx76(
7506
+ Button,
7507
+ {
7508
+ variant: "surface",
7509
+ size: "icon-xl",
7510
+ "aria-label": "Edit amount and description",
7511
+ onClick: () => setShowEdit(true),
7512
+ children: /* @__PURE__ */ jsx76(Icon, { name: "edit", size: "lg" })
7513
+ }
7514
+ )
7515
+ ] })
7497
7516
  ] }),
7498
7517
  invoiceStatus && /* @__PURE__ */ jsx76(
7499
7518
  InvoiceStatusBanner,
@@ -7512,9 +7531,8 @@ function BtcUnifiedReceive({
7512
7531
  "div",
7513
7532
  {
7514
7533
  className: cn(
7515
- "group flex cursor-pointer items-center gap-2 rounded-xl border bg-white/3 px-2.5 py-1.5",
7516
- "transition-all hover:bg-white/6 active:scale-[0.98]",
7517
- network.border
7534
+ "group flex cursor-pointer items-center gap-2 rounded-xl bg-white/3 px-2.5 py-1.5",
7535
+ "transition-all hover:bg-white/6 active:scale-[0.98]"
7518
7536
  ),
7519
7537
  style: { borderLeftWidth: 3, borderLeftColor: network.color },
7520
7538
  onClick: () => void copyToClipboard(address.value),
@@ -7538,36 +7556,48 @@ function BtcUnifiedReceive({
7538
7556
  })
7539
7557
  ] }),
7540
7558
  /* @__PURE__ */ jsx76(
7541
- NetworkInfoDisclosure,
7559
+ BottomSheet,
7542
7560
  {
7543
- networks: Array.from(
7544
- new Set(accountReceiveResult.addresses.map((address) => address.network))
7545
- )
7561
+ open: showEdit,
7562
+ title: "Edit request",
7563
+ icon: /* @__PURE__ */ jsx76(Icon, { name: "edit", size: "md" }),
7564
+ onClose: () => setShowEdit(false),
7565
+ children: /* @__PURE__ */ jsxs62("div", { className: "space-y-4", children: [
7566
+ /* @__PURE__ */ jsxs62("div", { className: "space-y-1.5", children: [
7567
+ /* @__PURE__ */ jsx76("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Amount (optional)" }),
7568
+ /* @__PURE__ */ jsx76(
7569
+ "input",
7570
+ {
7571
+ type: "text",
7572
+ value: amount,
7573
+ onChange: handleAmountChange,
7574
+ placeholder: "Any amount",
7575
+ className: "w-full rounded-xl bg-white/5 px-3 py-2.5 font-mono text-sm font-bold text-white shadow-inner transition-all placeholder:text-white/25 focus:outline focus:outline-2 focus:outline-primary/40",
7576
+ inputMode: "decimal"
7577
+ }
7578
+ ),
7579
+ amount && loading && /* @__PURE__ */ jsxs62("p", { className: "flex items-center gap-1 text-xxs text-warning/70", children: [
7580
+ /* @__PURE__ */ jsx76("span", { className: "material-symbols-outlined animate-spin text-icon-xxs", children: "progress_activity" }),
7581
+ "Updating invoice..."
7582
+ ] })
7583
+ ] }),
7584
+ onDescriptionChange && /* @__PURE__ */ jsxs62("div", { className: "space-y-1.5", children: [
7585
+ /* @__PURE__ */ jsx76("label", { className: "text-xxs font-bold uppercase tracking-widest text-white/40", children: "Description (optional)" }),
7586
+ /* @__PURE__ */ jsx76(
7587
+ "input",
7588
+ {
7589
+ type: "text",
7590
+ value: description ?? "",
7591
+ onChange: (event) => onDescriptionChange(event.target.value),
7592
+ placeholder: "What's this for?",
7593
+ className: "w-full rounded-xl bg-white/5 px-3 py-2.5 text-sm text-white shadow-inner transition-all placeholder:text-white/25 focus:outline focus:outline-2 focus:outline-primary/40"
7594
+ }
7595
+ )
7596
+ ] }),
7597
+ /* @__PURE__ */ jsx76(Button, { variant: "cta", className: "w-full", onClick: () => setShowEdit(false), children: "Done" })
7598
+ ] })
7546
7599
  }
7547
- ),
7548
- /* @__PURE__ */ jsxs62("div", { className: "flex gap-2.5 pt-1", children: [
7549
- showRegenerate && /* @__PURE__ */ jsxs62(
7550
- "button",
7551
- {
7552
- type: "button",
7553
- onClick: () => {
7554
- setAddress("");
7555
- setAmount("");
7556
- setInvoiceStatus(null);
7557
- setAccountReceiveResult(null);
7558
- },
7559
- className: "flex flex-1 items-center justify-center gap-1.5 rounded-xl border py-3 text-xs font-bold text-muted-foreground transition-all hover:border-border hover:bg-accent hover:text-white active:scale-[0.98]",
7560
- children: [
7561
- /* @__PURE__ */ jsx76("span", { className: "material-symbols-outlined text-icon-sm", children: "refresh" }),
7562
- "New Address"
7563
- ]
7564
- }
7565
- ),
7566
- /* @__PURE__ */ jsxs62(Button, { variant: "cta", onClick: handleDone, className: showRegenerate ? void 0 : "flex-1", children: [
7567
- /* @__PURE__ */ jsx76("span", { className: "material-symbols-outlined text-icon-sm", children: "check" }),
7568
- "Done"
7569
- ] })
7570
- ] })
7600
+ )
7571
7601
  ] });
7572
7602
  }
7573
7603
 
@@ -7989,7 +8019,7 @@ function DepositPreGeneration({
7989
8019
  }
7990
8020
 
7991
8021
  // src/web/components/deposit-asset-selection.tsx
7992
- import { useEffect as useEffect10, useState as useState15 } from "react";
8022
+ import { useEffect as useEffect10, useState as useState16 } from "react";
7993
8023
  import { jsx as jsx79, jsxs as jsxs65 } from "react/jsx-runtime";
7994
8024
  var ADD_ASSET_SUBTITLE = {
7995
8025
  RGB: "RGB asset on Bitcoin",
@@ -8032,8 +8062,8 @@ function DepositAssetSelection({
8032
8062
  const ownedAssetsCount = ownedAssets.length;
8033
8063
  const noResults = filteredAssets.length === 0 && !!searchQuery;
8034
8064
  const isSearching = !!searchQuery;
8035
- const [assetsExpanded, setAssetsExpanded] = useState15(false);
8036
- const [showAddAssetModal, setShowAddAssetModal] = useState15(false);
8065
+ const [assetsExpanded, setAssetsExpanded] = useState16(false);
8066
+ const [showAddAssetModal, setShowAddAssetModal] = useState16(false);
8037
8067
  useEffect10(() => {
8038
8068
  if (isSearching) setAssetsExpanded(true);
8039
8069
  }, [isSearching]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaleido-ui",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "description": "KaleidoSwap shared UI library — design tokens, web components (Tailwind + Radix), and React Native components extending WDK UI Kit",
5
5
  "license": "MIT",
6
6
  "type": "module",