@orangecheck/design 0.7.4 → 0.7.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/dist/components.js +31 -39
- package/dist/components.js.map +1 -1
- package/dist/components.mjs +30 -38
- package/dist/components.mjs.map +1 -1
- package/dist/composites.d.mts +5 -3
- package/dist/composites.d.ts +5 -3
- package/dist/composites.js +31 -39
- package/dist/composites.js.map +1 -1
- package/dist/composites.mjs +30 -38
- package/dist/composites.mjs.map +1 -1
- package/dist/format.d.mts +12 -1
- package/dist/format.d.ts +12 -1
- package/dist/format.js +48 -0
- package/dist/format.js.map +1 -1
- package/dist/format.mjs +47 -1
- package/dist/format.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +73 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -12
- package/dist/index.mjs.map +1 -1
- package/dist/primitives.js.map +1 -1
- package/dist/primitives.mjs.map +1 -1
- package/package.json +2 -2
package/dist/format.js
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/format/use-spot-price.ts
|
|
6
|
+
function useSpotPrice(opts = {}) {
|
|
7
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
8
|
+
const [state, setState] = react.useState({
|
|
9
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
10
|
+
fetchedAt: null
|
|
11
|
+
});
|
|
12
|
+
react.useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
async function pull() {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(endpoint);
|
|
17
|
+
if (!res.ok) return;
|
|
18
|
+
const json = await res.json();
|
|
19
|
+
if (cancelled) return;
|
|
20
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
21
|
+
setState({
|
|
22
|
+
btcUsd: Math.round(json.btc_usd),
|
|
23
|
+
fetchedAt: json.fetched_at ?? null
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
void pull();
|
|
30
|
+
const t = setInterval(pull, intervalMs);
|
|
31
|
+
return () => {
|
|
32
|
+
cancelled = true;
|
|
33
|
+
clearInterval(t);
|
|
34
|
+
};
|
|
35
|
+
}, [endpoint, intervalMs]);
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
// src/format/index.ts
|
|
4
40
|
var REFERENCE_BTC_USD = 95e3;
|
|
5
41
|
function formatSats(sats) {
|
|
@@ -28,6 +64,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
28
64
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
29
65
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
30
66
|
}
|
|
67
|
+
function asOf(iso) {
|
|
68
|
+
if (!iso) return null;
|
|
69
|
+
const t = new Date(iso);
|
|
70
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
71
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
72
|
+
hour: "2-digit",
|
|
73
|
+
minute: "2-digit",
|
|
74
|
+
hour12: false
|
|
75
|
+
})}`;
|
|
76
|
+
}
|
|
31
77
|
function relativeTime(when) {
|
|
32
78
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
33
79
|
if (Number.isNaN(t)) return "";
|
|
@@ -56,6 +102,7 @@ function explorerUrl(target, network = "mainnet") {
|
|
|
56
102
|
}
|
|
57
103
|
|
|
58
104
|
exports.REFERENCE_BTC_USD = REFERENCE_BTC_USD;
|
|
105
|
+
exports.asOf = asOf;
|
|
59
106
|
exports.explorerUrl = explorerUrl;
|
|
60
107
|
exports.formatSats = formatSats;
|
|
61
108
|
exports.formatSatsCompact = formatSatsCompact;
|
|
@@ -64,5 +111,6 @@ exports.relativeTime = relativeTime;
|
|
|
64
111
|
exports.satsToUsd = satsToUsd;
|
|
65
112
|
exports.shortenAddress = shortenAddress;
|
|
66
113
|
exports.usdToSats = usdToSats;
|
|
114
|
+
exports.useSpotPrice = useSpotPrice;
|
|
67
115
|
//# sourceMappingURL=format.js.map
|
|
68
116
|
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format/index.ts"],"names":[],"mappings":"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","file":"format.js","sourcesContent":["/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n */\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/format/use-spot-price.ts","../src/format/index.ts"],"names":["useState","useEffect"],"mappings":"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","file":"format.js","sourcesContent":["/**\n * useSpotPrice — live BTC/USD spot rate for display surfaces.\n *\n * Pulls a JSON shape `{ btc_usd: number, fetched_at?: string | null }`\n * from a consumer-hosted endpoint (default `/api/price/btc-usd`),\n * refreshing every 60 seconds on a setInterval. The initial paint\n * reads `REFERENCE_BTC_USD` so the first frame is never NaN.\n *\n * Consumer contract (read this when wiring):\n *\n * The hook makes a relative fetch to `endpoint`. Every consumer that\n * mounts this MUST host that route locally — typically a Next.js\n * pages-router API route that proxies mempool.space's spot feed and\n * caches in-process. The canonical implementation lives at\n * `oc-vault-web/src/pages/api/price/btc-usd.ts` (and the mirror in\n * `oc-me-web`). Both also host the matching server-side fetch +\n * cache in `src/lib/price/feed.ts` — that part stays per-consumer\n * because it's Node-only code that doesn't belong in a UI package.\n *\n * If you don't have the route yet, pass an explicit endpoint\n * (e.g. `useSpotPrice({ endpoint: 'https://other-site.ochk.io/api/price/btc-usd' })`)\n * pointing at a sibling site's endpoint — they're CORS-open.\n *\n * Returns the rich `{ btcUsd, fetchedAt }` shape rather than a bare\n * number so callers that want a provenance caption can render\n * `asOf(fetchedAt)` without a second hook.\n */\n\n'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { REFERENCE_BTC_USD } from './index';\n\nexport interface SpotPrice {\n /** BTC/USD rate. Falls back to `REFERENCE_BTC_USD` until the first\n * fetch lands so the initial paint never reads NaN. */\n btcUsd: number;\n /** ISO timestamp the rate was fetched upstream. `null` until the\n * first successful fetch. Pair with `asOf()` for \"as of HH:MM\". */\n fetchedAt: string | null;\n}\n\nexport interface UseSpotPriceOptions {\n /** Override the endpoint path. Defaults to `/api/price/btc-usd` —\n * the family-canonical proxy route. Useful when consuming a sibling\n * site's endpoint cross-origin, or when self-hosting the proxy at\n * a non-default path. */\n endpoint?: string;\n /** Override the refresh cadence in milliseconds. Defaults to 60_000,\n * matching the upstream mempool.space cache TTL. Lower than ~10s\n * is wasted bandwidth — the underlying feed doesn't refresh faster. */\n intervalMs?: number;\n}\n\ninterface SpotPriceResponse {\n btc_usd?: number;\n fetched_at?: string | null;\n}\n\n/**\n * Hook · returns the latest BTC/USD spot rate from `endpoint`.\n *\n * Behaviour:\n * - First mount: synchronous return of `REFERENCE_BTC_USD` (no NaN),\n * then an async fetch updates state.\n * - Every `intervalMs` thereafter, a background fetch keeps the rate\n * fresh while the tab is alive.\n * - Network blips keep the last-good value — no flash to fallback.\n * - Unmount cancels both the in-flight read (`cancelled` flag) and\n * the interval timer.\n */\nexport function useSpotPrice(opts: UseSpotPriceOptions = {}): SpotPrice {\n const { endpoint = '/api/price/btc-usd', intervalMs = 60_000 } = opts;\n const [state, setState] = useState<SpotPrice>({\n btcUsd: REFERENCE_BTC_USD,\n fetchedAt: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n async function pull() {\n try {\n const res = await fetch(endpoint);\n if (!res.ok) return;\n const json = (await res.json()) as SpotPriceResponse;\n if (cancelled) return;\n if (typeof json.btc_usd === 'number' && json.btc_usd > 0) {\n setState({\n btcUsd: Math.round(json.btc_usd),\n fetchedAt: json.fetched_at ?? null,\n });\n }\n } catch {\n // Network blip · keep the last good value, don't flash a\n // fallback. The next poll will recover.\n }\n }\n void pull();\n const t = setInterval(pull, intervalMs);\n return () => {\n cancelled = true;\n clearInterval(t);\n };\n }, [endpoint, intervalMs]);\n\n return state;\n}\n","/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n *\n * Includes the `useSpotPrice` React hook for live BTC/USD display. The hook\n * fetches a consumer-hosted endpoint — see `use-spot-price.ts` for the\n * one-route-per-consumer contract.\n */\n\nexport { useSpotPrice, type SpotPrice, type UseSpotPriceOptions } from './use-spot-price';\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/**\n * \"as of HH:MM\" provenance footnote. Returns `null` for nullish or\n * unparseable input so callers can render nothing\n * (`{x && <span>{x}</span>}`) rather than gate on a magic empty\n * string. 24-hour, locale-aware. Pairs with `useSpotPrice().fetchedAt`\n * to caption a live-rate display.\n */\nexport function asOf(iso: string | null | undefined): string | null {\n if (!iso) return null;\n const t = new Date(iso);\n if (Number.isNaN(t.getTime())) return null;\n return `as of ${t.toLocaleTimeString(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n })}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
package/dist/format.mjs
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/format/use-spot-price.ts
|
|
4
|
+
function useSpotPrice(opts = {}) {
|
|
5
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
6
|
+
const [state, setState] = useState({
|
|
7
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
8
|
+
fetchedAt: null
|
|
9
|
+
});
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
async function pull() {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(endpoint);
|
|
15
|
+
if (!res.ok) return;
|
|
16
|
+
const json = await res.json();
|
|
17
|
+
if (cancelled) return;
|
|
18
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
19
|
+
setState({
|
|
20
|
+
btcUsd: Math.round(json.btc_usd),
|
|
21
|
+
fetchedAt: json.fetched_at ?? null
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
void pull();
|
|
28
|
+
const t = setInterval(pull, intervalMs);
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true;
|
|
31
|
+
clearInterval(t);
|
|
32
|
+
};
|
|
33
|
+
}, [endpoint, intervalMs]);
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
|
|
1
37
|
// src/format/index.ts
|
|
2
38
|
var REFERENCE_BTC_USD = 95e3;
|
|
3
39
|
function formatSats(sats) {
|
|
@@ -26,6 +62,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
26
62
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
27
63
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
28
64
|
}
|
|
65
|
+
function asOf(iso) {
|
|
66
|
+
if (!iso) return null;
|
|
67
|
+
const t = new Date(iso);
|
|
68
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
69
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
70
|
+
hour: "2-digit",
|
|
71
|
+
minute: "2-digit",
|
|
72
|
+
hour12: false
|
|
73
|
+
})}`;
|
|
74
|
+
}
|
|
29
75
|
function relativeTime(when) {
|
|
30
76
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
31
77
|
if (Number.isNaN(t)) return "";
|
|
@@ -53,6 +99,6 @@ function explorerUrl(target, network = "mainnet") {
|
|
|
53
99
|
return `${base}/block/${target.block}`;
|
|
54
100
|
}
|
|
55
101
|
|
|
56
|
-
export { REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
102
|
+
export { REFERENCE_BTC_USD, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
|
57
103
|
//# sourceMappingURL=format.mjs.map
|
|
58
104
|
//# sourceMappingURL=format.mjs.map
|
package/dist/format.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format/index.ts"],"names":[],"mappings":"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","file":"format.mjs","sourcesContent":["/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n */\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/format/use-spot-price.ts","../src/format/index.ts"],"names":[],"mappings":"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","file":"format.mjs","sourcesContent":["/**\n * useSpotPrice — live BTC/USD spot rate for display surfaces.\n *\n * Pulls a JSON shape `{ btc_usd: number, fetched_at?: string | null }`\n * from a consumer-hosted endpoint (default `/api/price/btc-usd`),\n * refreshing every 60 seconds on a setInterval. The initial paint\n * reads `REFERENCE_BTC_USD` so the first frame is never NaN.\n *\n * Consumer contract (read this when wiring):\n *\n * The hook makes a relative fetch to `endpoint`. Every consumer that\n * mounts this MUST host that route locally — typically a Next.js\n * pages-router API route that proxies mempool.space's spot feed and\n * caches in-process. The canonical implementation lives at\n * `oc-vault-web/src/pages/api/price/btc-usd.ts` (and the mirror in\n * `oc-me-web`). Both also host the matching server-side fetch +\n * cache in `src/lib/price/feed.ts` — that part stays per-consumer\n * because it's Node-only code that doesn't belong in a UI package.\n *\n * If you don't have the route yet, pass an explicit endpoint\n * (e.g. `useSpotPrice({ endpoint: 'https://other-site.ochk.io/api/price/btc-usd' })`)\n * pointing at a sibling site's endpoint — they're CORS-open.\n *\n * Returns the rich `{ btcUsd, fetchedAt }` shape rather than a bare\n * number so callers that want a provenance caption can render\n * `asOf(fetchedAt)` without a second hook.\n */\n\n'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { REFERENCE_BTC_USD } from './index';\n\nexport interface SpotPrice {\n /** BTC/USD rate. Falls back to `REFERENCE_BTC_USD` until the first\n * fetch lands so the initial paint never reads NaN. */\n btcUsd: number;\n /** ISO timestamp the rate was fetched upstream. `null` until the\n * first successful fetch. Pair with `asOf()` for \"as of HH:MM\". */\n fetchedAt: string | null;\n}\n\nexport interface UseSpotPriceOptions {\n /** Override the endpoint path. Defaults to `/api/price/btc-usd` —\n * the family-canonical proxy route. Useful when consuming a sibling\n * site's endpoint cross-origin, or when self-hosting the proxy at\n * a non-default path. */\n endpoint?: string;\n /** Override the refresh cadence in milliseconds. Defaults to 60_000,\n * matching the upstream mempool.space cache TTL. Lower than ~10s\n * is wasted bandwidth — the underlying feed doesn't refresh faster. */\n intervalMs?: number;\n}\n\ninterface SpotPriceResponse {\n btc_usd?: number;\n fetched_at?: string | null;\n}\n\n/**\n * Hook · returns the latest BTC/USD spot rate from `endpoint`.\n *\n * Behaviour:\n * - First mount: synchronous return of `REFERENCE_BTC_USD` (no NaN),\n * then an async fetch updates state.\n * - Every `intervalMs` thereafter, a background fetch keeps the rate\n * fresh while the tab is alive.\n * - Network blips keep the last-good value — no flash to fallback.\n * - Unmount cancels both the in-flight read (`cancelled` flag) and\n * the interval timer.\n */\nexport function useSpotPrice(opts: UseSpotPriceOptions = {}): SpotPrice {\n const { endpoint = '/api/price/btc-usd', intervalMs = 60_000 } = opts;\n const [state, setState] = useState<SpotPrice>({\n btcUsd: REFERENCE_BTC_USD,\n fetchedAt: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n async function pull() {\n try {\n const res = await fetch(endpoint);\n if (!res.ok) return;\n const json = (await res.json()) as SpotPriceResponse;\n if (cancelled) return;\n if (typeof json.btc_usd === 'number' && json.btc_usd > 0) {\n setState({\n btcUsd: Math.round(json.btc_usd),\n fetchedAt: json.fetched_at ?? null,\n });\n }\n } catch {\n // Network blip · keep the last good value, don't flash a\n // fallback. The next poll will recover.\n }\n }\n void pull();\n const t = setInterval(pull, intervalMs);\n return () => {\n cancelled = true;\n clearInterval(t);\n };\n }, [endpoint, intervalMs]);\n\n return state;\n}\n","/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n *\n * Includes the `useSpotPrice` React hook for live BTC/USD display. The hook\n * fetches a consumer-hosted endpoint — see `use-spot-price.ts` for the\n * one-route-per-consumer contract.\n */\n\nexport { useSpotPrice, type SpotPrice, type UseSpotPriceOptions } from './use-spot-price';\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/**\n * \"as of HH:MM\" provenance footnote. Returns `null` for nullish or\n * unparseable input so callers can render nothing\n * (`{x && <span>{x}</span>}`) rather than gate on a magic empty\n * string. 24-hour, locale-aware. Pairs with `useSpotPrice().fetchedAt`\n * to caption a live-rate display.\n */\nexport function asOf(iso: string | null | undefined): string | null {\n if (!iso) return null;\n const t = new Date(iso);\n if (Number.isNaN(t.getTime())) return null;\n return `as of ${t.toLocaleTimeString(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n })}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppea
|
|
|
2
2
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithActionProps, AlertWithCountdown, AlertWithCountdownProps, Badge, Bar, BarProps, BitcoinAddress, BitcoinAddressProps, Button, Card, CardProps, Checkbox, ConfirmHost, ConfirmOptions, CopyButton, CopyButtonProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, ModalProps, Pagination, PaginationProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, PromptOptions, QrCode, QrCodeProps, RadioGroup, RadioGroupItem, SatsAmount, SatsAmountProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, SkeletonProps, Sparkline, SparklineProps, StatBlock, StatBlockProps, StatusPill, StatusPillProps, StatusPillSpec, StatusPillVariant, StatusTone, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, WorkingProps, badgeVariants, buttonVariants, confirm, makeStatusPill, prompt } from './primitives.mjs';
|
|
3
3
|
export { Callout, CalloutProps, CalloutTone, DataRow, DataRowProps, DefinitionItem, DefinitionList, DefinitionListProps, EmptyState, EmptyStateCta, EmptyStateProps, HelpHint, HelpHintProps, SectionHeader, SectionHeaderProps, StatCard, StatCardProps, StatGrid, StatGridProps, StatItem, StatTile } from './composites.mjs';
|
|
4
4
|
export { AppShell, AppShellProps, EcosystemSlug, EcosystemSwitcher, EcosystemSwitcherProps, FAMILY_PROPERTIES, FamilyCategory, FamilyProperty, LayoutSubHeader, LayoutSubHeaderProps, LayoutSubHeaderTag, OcAccountMenu, OcAccountMenuBuildInfo, OcAccountMenuItem, OcAccountMenuProps, OcAccountMenuSession, OcAccountMenuView, OcAccountMenuViewProps, OcDashboardHub, OcDashboardHubProps, OcDashboardShell, OcDashboardShellProps, OcDashboardTool, OcLogoDropdown, OcLogoDropdownProps, OcPrimaryNav, OcPrimaryNavLink, OcPrimaryNavProps, SITE_STATE_LABEL, SiteState, findFamilyProperty } from './chrome.mjs';
|
|
5
|
-
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats } from './format.mjs';
|
|
5
|
+
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, SpotPrice, UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice } from './format.mjs';
|
|
6
6
|
import 'clsx';
|
|
7
7
|
import 'react/jsx-runtime';
|
|
8
8
|
import 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppea
|
|
|
2
2
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithActionProps, AlertWithCountdown, AlertWithCountdownProps, Badge, Bar, BarProps, BitcoinAddress, BitcoinAddressProps, Button, Card, CardProps, Checkbox, ConfirmHost, ConfirmOptions, CopyButton, CopyButtonProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, ModalProps, Pagination, PaginationProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, PromptOptions, QrCode, QrCodeProps, RadioGroup, RadioGroupItem, SatsAmount, SatsAmountProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, SkeletonProps, Sparkline, SparklineProps, StatBlock, StatBlockProps, StatusPill, StatusPillProps, StatusPillSpec, StatusPillVariant, StatusTone, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, WorkingProps, badgeVariants, buttonVariants, confirm, makeStatusPill, prompt } from './primitives.js';
|
|
3
3
|
export { Callout, CalloutProps, CalloutTone, DataRow, DataRowProps, DefinitionItem, DefinitionList, DefinitionListProps, EmptyState, EmptyStateCta, EmptyStateProps, HelpHint, HelpHintProps, SectionHeader, SectionHeaderProps, StatCard, StatCardProps, StatGrid, StatGridProps, StatItem, StatTile } from './composites.js';
|
|
4
4
|
export { AppShell, AppShellProps, EcosystemSlug, EcosystemSwitcher, EcosystemSwitcherProps, FAMILY_PROPERTIES, FamilyCategory, FamilyProperty, LayoutSubHeader, LayoutSubHeaderProps, LayoutSubHeaderTag, OcAccountMenu, OcAccountMenuBuildInfo, OcAccountMenuItem, OcAccountMenuProps, OcAccountMenuSession, OcAccountMenuView, OcAccountMenuViewProps, OcDashboardHub, OcDashboardHubProps, OcDashboardShell, OcDashboardShellProps, OcDashboardTool, OcLogoDropdown, OcLogoDropdownProps, OcPrimaryNav, OcPrimaryNavLink, OcPrimaryNavProps, SITE_STATE_LABEL, SiteState, findFamilyProperty } from './chrome.js';
|
|
5
|
-
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats } from './format.js';
|
|
5
|
+
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, SpotPrice, UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice } from './format.js';
|
|
6
6
|
import 'clsx';
|
|
7
7
|
import 'react/jsx-runtime';
|
|
8
8
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -1939,6 +1939,38 @@ function TabsContent({ className, ...props }) {
|
|
|
1939
1939
|
}
|
|
1940
1940
|
);
|
|
1941
1941
|
}
|
|
1942
|
+
function useSpotPrice(opts = {}) {
|
|
1943
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
1944
|
+
const [state, setState] = React2.useState({
|
|
1945
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
1946
|
+
fetchedAt: null
|
|
1947
|
+
});
|
|
1948
|
+
React2.useEffect(() => {
|
|
1949
|
+
let cancelled = false;
|
|
1950
|
+
async function pull() {
|
|
1951
|
+
try {
|
|
1952
|
+
const res = await fetch(endpoint);
|
|
1953
|
+
if (!res.ok) return;
|
|
1954
|
+
const json = await res.json();
|
|
1955
|
+
if (cancelled) return;
|
|
1956
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
1957
|
+
setState({
|
|
1958
|
+
btcUsd: Math.round(json.btc_usd),
|
|
1959
|
+
fetchedAt: json.fetched_at ?? null
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
} catch {
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
void pull();
|
|
1966
|
+
const t = setInterval(pull, intervalMs);
|
|
1967
|
+
return () => {
|
|
1968
|
+
cancelled = true;
|
|
1969
|
+
clearInterval(t);
|
|
1970
|
+
};
|
|
1971
|
+
}, [endpoint, intervalMs]);
|
|
1972
|
+
return state;
|
|
1973
|
+
}
|
|
1942
1974
|
|
|
1943
1975
|
// src/format/index.ts
|
|
1944
1976
|
var REFERENCE_BTC_USD = 95e3;
|
|
@@ -1968,6 +2000,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
1968
2000
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
1969
2001
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
1970
2002
|
}
|
|
2003
|
+
function asOf(iso) {
|
|
2004
|
+
if (!iso) return null;
|
|
2005
|
+
const t = new Date(iso);
|
|
2006
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
2007
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
2008
|
+
hour: "2-digit",
|
|
2009
|
+
minute: "2-digit",
|
|
2010
|
+
hour12: false
|
|
2011
|
+
})}`;
|
|
2012
|
+
}
|
|
1971
2013
|
function relativeTime(when) {
|
|
1972
2014
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
1973
2015
|
if (Number.isNaN(t)) return "";
|
|
@@ -2109,28 +2151,46 @@ function QrCode({ value, size = 200, label, className }) {
|
|
|
2109
2151
|
);
|
|
2110
2152
|
}
|
|
2111
2153
|
function HelpHint({
|
|
2112
|
-
title,
|
|
2113
2154
|
children,
|
|
2114
|
-
|
|
2115
|
-
|
|
2155
|
+
title,
|
|
2156
|
+
side = "top",
|
|
2157
|
+
size = "sm",
|
|
2158
|
+
tone = "default",
|
|
2159
|
+
label = "what is this?",
|
|
2116
2160
|
className
|
|
2117
2161
|
}) {
|
|
2118
|
-
|
|
2162
|
+
const iconSize = size === "xs" ? 12 : 14;
|
|
2163
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(PopoverPrimitive__namespace.Root, { children: [
|
|
2119
2164
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2120
|
-
|
|
2165
|
+
PopoverPrimitive__namespace.Trigger,
|
|
2121
2166
|
{
|
|
2122
2167
|
"aria-label": label,
|
|
2168
|
+
onClick: (e) => e.stopPropagation(),
|
|
2123
2169
|
className: cn(
|
|
2124
|
-
"
|
|
2170
|
+
"focus-visible:ring-primary/60 inline-flex shrink-0 items-center justify-center rounded-full transition-colors outline-none focus-visible:ring-2",
|
|
2171
|
+
tone === "subtle" ? "text-muted-foreground/60 hover:text-foreground/80" : "text-primary/70 hover:text-primary",
|
|
2125
2172
|
className
|
|
2126
2173
|
),
|
|
2127
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HelpCircle, {
|
|
2174
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HelpCircle, { size: iconSize, "aria-hidden": true })
|
|
2128
2175
|
}
|
|
2129
2176
|
),
|
|
2130
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2177
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2178
|
+
PopoverPrimitive__namespace.Content,
|
|
2179
|
+
{
|
|
2180
|
+
side,
|
|
2181
|
+
sideOffset: 6,
|
|
2182
|
+
collisionPadding: 12,
|
|
2183
|
+
className: "border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 max-w-xs rounded-md border p-3 text-xs leading-relaxed shadow-md outline-none",
|
|
2184
|
+
children: [
|
|
2185
|
+
title && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "label-mono text-primary mb-1.5", children: [
|
|
2186
|
+
"\xA7 ",
|
|
2187
|
+
title
|
|
2188
|
+
] }),
|
|
2189
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-foreground/90", children }),
|
|
2190
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Arrow, { className: "fill-popover" })
|
|
2191
|
+
]
|
|
2192
|
+
}
|
|
2193
|
+
) })
|
|
2134
2194
|
] });
|
|
2135
2195
|
}
|
|
2136
2196
|
var TONE_CLASS = {
|
|
@@ -4012,6 +4072,7 @@ exports.Toaster = Toaster;
|
|
|
4012
4072
|
exports.Working = Working;
|
|
4013
4073
|
exports.WorkingPanel = WorkingPanel;
|
|
4014
4074
|
exports.accentFor = accentFor;
|
|
4075
|
+
exports.asOf = asOf;
|
|
4015
4076
|
exports.badgeVariants = badgeVariants;
|
|
4016
4077
|
exports.buttonVariants = buttonVariants;
|
|
4017
4078
|
exports.cn = cn;
|
|
@@ -4031,5 +4092,6 @@ exports.satsToUsd = satsToUsd;
|
|
|
4031
4092
|
exports.shortenAddress = shortenAddress;
|
|
4032
4093
|
exports.usdToSats = usdToSats;
|
|
4033
4094
|
exports.useOcSkin = useOcSkin;
|
|
4095
|
+
exports.useSpotPrice = useSpotPrice;
|
|
4034
4096
|
//# sourceMappingURL=index.js.map
|
|
4035
4097
|
//# sourceMappingURL=index.js.map
|