@orangecheck/design 0.7.5 → 0.8.0
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/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 +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +59 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -3
- package/dist/index.mjs.map +1 -1
- package/dist/primitives.d.mts +3 -3
- package/dist/primitives.d.ts +3 -3
- package/dist/primitives.js.map +1 -1
- package/dist/primitives.mjs.map +1 -1
- package/dist/styles/aurora.css +224 -0
- package/dist/styles.css +1 -0
- package/dist/tokens.d.mts +11 -2
- package/dist/tokens.d.ts +11 -2
- package/dist/tokens.js +15 -2
- package/dist/tokens.js.map +1 -1
- package/dist/tokens.mjs +15 -3
- package/dist/tokens.mjs.map +1 -1
- package/package.json +2 -2
package/dist/format.d.mts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
interface SpotPrice {
|
|
2
|
+
btcUsd: number;
|
|
3
|
+
fetchedAt: string | null;
|
|
4
|
+
}
|
|
5
|
+
interface UseSpotPriceOptions {
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
intervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function useSpotPrice(opts?: UseSpotPriceOptions): SpotPrice;
|
|
10
|
+
|
|
1
11
|
declare const REFERENCE_BTC_USD = 95000;
|
|
2
12
|
declare function formatSats(sats: number): string;
|
|
3
13
|
declare function formatSatsCompact(sats: number): string;
|
|
4
14
|
declare function satsToUsd(sats: number, btcUsd?: number): string;
|
|
5
15
|
declare function usdToSats(usd: number, btcUsd?: number): number;
|
|
6
16
|
declare function priceBoth(sats: number, btcUsd?: number): string;
|
|
17
|
+
declare function asOf(iso: string | null | undefined): string | null;
|
|
7
18
|
declare function relativeTime(when: string | number | Date): string;
|
|
8
19
|
declare function shortenAddress(value: string, head?: number, tail?: number): string;
|
|
9
20
|
type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';
|
|
@@ -16,4 +27,4 @@ type ExplorerTarget = {
|
|
|
16
27
|
};
|
|
17
28
|
declare function explorerUrl(target: ExplorerTarget, network?: ExplorerNetwork): string;
|
|
18
29
|
|
|
19
|
-
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
30
|
+
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, type SpotPrice, type UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
package/dist/format.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
interface SpotPrice {
|
|
2
|
+
btcUsd: number;
|
|
3
|
+
fetchedAt: string | null;
|
|
4
|
+
}
|
|
5
|
+
interface UseSpotPriceOptions {
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
intervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function useSpotPrice(opts?: UseSpotPriceOptions): SpotPrice;
|
|
10
|
+
|
|
1
11
|
declare const REFERENCE_BTC_USD = 95000;
|
|
2
12
|
declare function formatSats(sats: number): string;
|
|
3
13
|
declare function formatSatsCompact(sats: number): string;
|
|
4
14
|
declare function satsToUsd(sats: number, btcUsd?: number): string;
|
|
5
15
|
declare function usdToSats(usd: number, btcUsd?: number): number;
|
|
6
16
|
declare function priceBoth(sats: number, btcUsd?: number): string;
|
|
17
|
+
declare function asOf(iso: string | null | undefined): string | null;
|
|
7
18
|
declare function relativeTime(when: string | number | Date): string;
|
|
8
19
|
declare function shortenAddress(value: string, head?: number, tail?: number): string;
|
|
9
20
|
type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';
|
|
@@ -16,4 +27,4 @@ type ExplorerTarget = {
|
|
|
16
27
|
};
|
|
17
28
|
declare function explorerUrl(target: ExplorerTarget, network?: ExplorerNetwork): string;
|
|
18
29
|
|
|
19
|
-
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
30
|
+
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, type SpotPrice, type UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, OcAppearanceMenuProps, OcTheme, OcThemeBridge, OcThemeId, OcThemePicker, OcThemePickerProps, OcThemeProvider, OcThemeProviderProps, accentFor, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin } from './tokens.mjs';
|
|
1
|
+
export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, OcAppearanceMenuProps, OcAurora, OcAuroraProps, OcTheme, OcThemeBridge, OcThemeId, OcThemePicker, OcThemePickerProps, OcThemeProvider, OcThemeProviderProps, accentFor, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin } from './tokens.mjs';
|
|
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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, OcAppearanceMenuProps, OcTheme, OcThemeBridge, OcThemeId, OcThemePicker, OcThemePickerProps, OcThemeProvider, OcThemeProviderProps, accentFor, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin } from './tokens.js';
|
|
1
|
+
export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, OcAppearanceMenuProps, OcAurora, OcAuroraProps, OcTheme, OcThemeBridge, OcThemeId, OcThemePicker, OcThemePickerProps, OcThemeProvider, OcThemeProviderProps, accentFor, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin } from './tokens.js';
|
|
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
|
@@ -102,6 +102,11 @@ function resolveTheme(id) {
|
|
|
102
102
|
return isKnownTheme(id) ? id : DEFAULT_OC_THEME;
|
|
103
103
|
}
|
|
104
104
|
var OC_SKIN_COOKIE = "oc_skin";
|
|
105
|
+
var BLOBS = [1, 2, 3, 4, 5];
|
|
106
|
+
function OcAurora({ intensity, className }) {
|
|
107
|
+
const style = intensity == null ? void 0 : { "--oc-aurora-intensity": String(intensity) };
|
|
108
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("oc-aurora", className), style, "aria-hidden": "true", children: BLOBS.map((n) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: `oc-aurora__blob oc-aurora__blob-${n}` }, n)) });
|
|
109
|
+
}
|
|
105
110
|
var ONE_YEAR = 60 * 60 * 24 * 365;
|
|
106
111
|
var ATTR = "data-oc-theme";
|
|
107
112
|
function readSkinCookie() {
|
|
@@ -145,7 +150,11 @@ function applySkinChrome(skin) {
|
|
|
145
150
|
if (meta) meta.setAttribute("content", accentFor(skin));
|
|
146
151
|
}
|
|
147
152
|
var OcSkinContext = React2.createContext(null);
|
|
148
|
-
function OcThemeProvider({
|
|
153
|
+
function OcThemeProvider({
|
|
154
|
+
children,
|
|
155
|
+
defaultSkin = DEFAULT_OC_THEME,
|
|
156
|
+
aurora = true
|
|
157
|
+
}) {
|
|
149
158
|
const [skin, setSkinState] = React2.useState(() => resolveTheme(defaultSkin));
|
|
150
159
|
const hydratedRef = React2.useRef(false);
|
|
151
160
|
React2.useEffect(() => {
|
|
@@ -183,7 +192,10 @@ function OcThemeProvider({ children, defaultSkin = DEFAULT_OC_THEME }) {
|
|
|
183
192
|
() => ({ skin, setSkin, themes: OC_THEMES }),
|
|
184
193
|
[skin, setSkin]
|
|
185
194
|
);
|
|
186
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
195
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(OcSkinContext.Provider, { value, children: [
|
|
196
|
+
aurora !== false && /* @__PURE__ */ jsxRuntime.jsx(OcAurora, { intensity: typeof aurora === "object" ? aurora.intensity : void 0 }),
|
|
197
|
+
children
|
|
198
|
+
] });
|
|
187
199
|
}
|
|
188
200
|
function useOcSkin() {
|
|
189
201
|
const ctx = React2.useContext(OcSkinContext);
|
|
@@ -1939,6 +1951,38 @@ function TabsContent({ className, ...props }) {
|
|
|
1939
1951
|
}
|
|
1940
1952
|
);
|
|
1941
1953
|
}
|
|
1954
|
+
function useSpotPrice(opts = {}) {
|
|
1955
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
1956
|
+
const [state, setState] = React2.useState({
|
|
1957
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
1958
|
+
fetchedAt: null
|
|
1959
|
+
});
|
|
1960
|
+
React2.useEffect(() => {
|
|
1961
|
+
let cancelled = false;
|
|
1962
|
+
async function pull() {
|
|
1963
|
+
try {
|
|
1964
|
+
const res = await fetch(endpoint);
|
|
1965
|
+
if (!res.ok) return;
|
|
1966
|
+
const json = await res.json();
|
|
1967
|
+
if (cancelled) return;
|
|
1968
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
1969
|
+
setState({
|
|
1970
|
+
btcUsd: Math.round(json.btc_usd),
|
|
1971
|
+
fetchedAt: json.fetched_at ?? null
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
} catch {
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
void pull();
|
|
1978
|
+
const t = setInterval(pull, intervalMs);
|
|
1979
|
+
return () => {
|
|
1980
|
+
cancelled = true;
|
|
1981
|
+
clearInterval(t);
|
|
1982
|
+
};
|
|
1983
|
+
}, [endpoint, intervalMs]);
|
|
1984
|
+
return state;
|
|
1985
|
+
}
|
|
1942
1986
|
|
|
1943
1987
|
// src/format/index.ts
|
|
1944
1988
|
var REFERENCE_BTC_USD = 95e3;
|
|
@@ -1968,6 +2012,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
1968
2012
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
1969
2013
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
1970
2014
|
}
|
|
2015
|
+
function asOf(iso) {
|
|
2016
|
+
if (!iso) return null;
|
|
2017
|
+
const t = new Date(iso);
|
|
2018
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
2019
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
2020
|
+
hour: "2-digit",
|
|
2021
|
+
minute: "2-digit",
|
|
2022
|
+
hour12: false
|
|
2023
|
+
})}`;
|
|
2024
|
+
}
|
|
1971
2025
|
function relativeTime(when) {
|
|
1972
2026
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
1973
2027
|
if (Number.isNaN(t)) return "";
|
|
@@ -3970,6 +4024,7 @@ exports.OC_THEMES = OC_THEMES;
|
|
|
3970
4024
|
exports.OcAccountMenu = OcAccountMenu;
|
|
3971
4025
|
exports.OcAccountMenuView = OcAccountMenuView;
|
|
3972
4026
|
exports.OcAppearanceMenu = OcAppearanceMenu;
|
|
4027
|
+
exports.OcAurora = OcAurora;
|
|
3973
4028
|
exports.OcDashboardHub = OcDashboardHub;
|
|
3974
4029
|
exports.OcDashboardShell = OcDashboardShell;
|
|
3975
4030
|
exports.OcLogoDropdown = OcLogoDropdown;
|
|
@@ -4030,6 +4085,7 @@ exports.Toaster = Toaster;
|
|
|
4030
4085
|
exports.Working = Working;
|
|
4031
4086
|
exports.WorkingPanel = WorkingPanel;
|
|
4032
4087
|
exports.accentFor = accentFor;
|
|
4088
|
+
exports.asOf = asOf;
|
|
4033
4089
|
exports.badgeVariants = badgeVariants;
|
|
4034
4090
|
exports.buttonVariants = buttonVariants;
|
|
4035
4091
|
exports.cn = cn;
|
|
@@ -4049,5 +4105,6 @@ exports.satsToUsd = satsToUsd;
|
|
|
4049
4105
|
exports.shortenAddress = shortenAddress;
|
|
4050
4106
|
exports.usdToSats = usdToSats;
|
|
4051
4107
|
exports.useOcSkin = useOcSkin;
|
|
4108
|
+
exports.useSpotPrice = useSpotPrice;
|
|
4052
4109
|
//# sourceMappingURL=index.js.map
|
|
4053
4110
|
//# sourceMappingURL=index.js.map
|