@orangecheck/design 0.2.0 → 0.4.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/index.js CHANGED
@@ -15,8 +15,11 @@ var PopoverPrimitive = require('@radix-ui/react-popover');
15
15
  var AccordionPrimitive = require('@radix-ui/react-accordion');
16
16
  var SelectPrimitive = require('@radix-ui/react-select');
17
17
  var sonner = require('sonner');
18
+ var QRCode = require('qrcode');
18
19
  var ui_star = require('@orangecheck/ui');
19
20
 
21
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
+
20
23
  function _interopNamespace(e) {
21
24
  if (e && e.__esModule) return e;
22
25
  var n = Object.create(null);
@@ -42,6 +45,7 @@ var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive)
42
45
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
43
46
  var AccordionPrimitive__namespace = /*#__PURE__*/_interopNamespace(AccordionPrimitive);
44
47
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
48
+ var QRCode__default = /*#__PURE__*/_interopDefault(QRCode);
45
49
  var ui_star__namespace = /*#__PURE__*/_interopNamespace(ui_star);
46
50
 
47
51
  var __defProp = Object.defineProperty;
@@ -75,8 +79,12 @@ __export(src_exports, {
75
79
  AlertWithAction: () => AlertWithAction,
76
80
  AlertWithCountdown: () => AlertWithCountdown,
77
81
  Badge: () => Badge,
82
+ Bar: () => Bar,
83
+ BitcoinAddress: () => BitcoinAddress,
78
84
  Button: () => Button,
85
+ Card: () => Card,
79
86
  ConfirmHost: () => ConfirmHost,
87
+ CopyButton: () => CopyButton,
80
88
  DEFAULT_OC_THEME: () => DEFAULT_OC_THEME,
81
89
  Dialog: () => Dialog,
82
90
  DialogClose: () => DialogClose,
@@ -94,6 +102,7 @@ __export(src_exports, {
94
102
  Modal: () => Modal,
95
103
  OC_SKIN_COOKIE: () => OC_SKIN_COOKIE,
96
104
  OC_THEMES: () => OC_THEMES,
105
+ OcAppearanceMenu: () => OcAppearanceMenu,
97
106
  OcThemeBridge: () => OcThemeBridge,
98
107
  OcThemePicker: () => OcThemePicker,
99
108
  OcThemeProvider: () => OcThemeProvider,
@@ -103,6 +112,9 @@ __export(src_exports, {
103
112
  PopoverContent: () => PopoverContent,
104
113
  PopoverTrigger: () => PopoverTrigger,
105
114
  PromptHost: () => PromptHost,
115
+ QrCode: () => QrCode,
116
+ REFERENCE_BTC_USD: () => REFERENCE_BTC_USD,
117
+ SatsAmount: () => SatsAmount,
106
118
  Select: () => Select,
107
119
  SelectContent: () => SelectContent,
108
120
  SelectGroup: () => SelectGroup,
@@ -123,6 +135,9 @@ __export(src_exports, {
123
135
  SheetTitle: () => SheetTitle,
124
136
  SheetTrigger: () => SheetTrigger,
125
137
  Skeleton: () => Skeleton,
138
+ Sparkline: () => Sparkline,
139
+ StatBlock: () => StatBlock,
140
+ StatusPill: () => StatusPill,
126
141
  Textarea: () => Textarea,
127
142
  ThemeToggle: () => ThemeToggle,
128
143
  ThemeToggleLink: () => ThemeToggleLink,
@@ -133,10 +148,18 @@ __export(src_exports, {
133
148
  buttonVariants: () => buttonVariants,
134
149
  cn: () => cn,
135
150
  confirm: () => confirm,
151
+ explorerUrl: () => explorerUrl,
152
+ formatSats: () => formatSats,
153
+ formatSatsCompact: () => formatSatsCompact,
136
154
  getOcThemeInitScript: () => getOcThemeInitScript,
137
155
  isKnownTheme: () => isKnownTheme,
156
+ priceBoth: () => priceBoth,
138
157
  prompt: () => prompt,
158
+ relativeTime: () => relativeTime,
139
159
  resolveTheme: () => resolveTheme,
160
+ satsToUsd: () => satsToUsd,
161
+ shortenAddress: () => shortenAddress,
162
+ usdToSats: () => usdToSats,
140
163
  useOcSkin: () => useOcSkin
141
164
  });
142
165
  function cn(...inputs) {
@@ -381,6 +404,115 @@ function OcThemePicker({ className, triggerClassName, popoverClassName }) {
381
404
  )
382
405
  ] });
383
406
  }
407
+ var MODES = [
408
+ { id: "light", label: "light", Icon: lucideReact.Sun },
409
+ { id: "dark", label: "dark", Icon: lucideReact.Moon },
410
+ { id: "system", label: "system", Icon: lucideReact.Monitor }
411
+ ];
412
+ function OcAppearanceMenu({
413
+ className,
414
+ triggerClassName,
415
+ popoverClassName
416
+ }) {
417
+ const { skin, setSkin, themes } = useOcSkin();
418
+ const { theme, setTheme } = nextThemes.useTheme();
419
+ const [open, setOpen] = React2.useState(false);
420
+ const [mounted, setMounted] = React2.useState(false);
421
+ const rootRef = React2.useRef(null);
422
+ React2.useEffect(() => setMounted(true), []);
423
+ React2.useEffect(() => {
424
+ if (!open) return;
425
+ function onDown(e) {
426
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
427
+ }
428
+ function onKey(e) {
429
+ if (e.key === "Escape") setOpen(false);
430
+ }
431
+ document.addEventListener("mousedown", onDown);
432
+ document.addEventListener("keydown", onKey);
433
+ return () => {
434
+ document.removeEventListener("mousedown", onDown);
435
+ document.removeEventListener("keydown", onKey);
436
+ };
437
+ }, [open]);
438
+ const activeMode = mounted ? theme ?? "system" : null;
439
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
440
+ /* @__PURE__ */ jsxRuntime.jsx(
441
+ "button",
442
+ {
443
+ type: "button",
444
+ "aria-label": "appearance settings",
445
+ "aria-haspopup": "menu",
446
+ "aria-expanded": open,
447
+ title: "appearance \xB7 mode + theme",
448
+ onClick: () => setOpen((v) => !v),
449
+ className: cn(
450
+ "text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
451
+ triggerClassName
452
+ ),
453
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4", "aria-hidden": true })
454
+ }
455
+ ),
456
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
457
+ "div",
458
+ {
459
+ role: "menu",
460
+ className: cn(
461
+ "bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md",
462
+ popoverClassName
463
+ ),
464
+ children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "label-mono text-muted-foreground px-3 pt-3 pb-2", children: "mode" }),
466
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-1 px-2 pb-2", children: MODES.map(({ id, label, Icon: Icon2 }) => {
467
+ const active = activeMode === id;
468
+ return /* @__PURE__ */ jsxRuntime.jsxs(
469
+ "button",
470
+ {
471
+ type: "button",
472
+ role: "menuitemradio",
473
+ "aria-checked": active,
474
+ onClick: () => setTheme(id),
475
+ className: cn(
476
+ "flex flex-col items-center gap-1 rounded-md border py-2 text-[11px] transition-colors",
477
+ active ? "border-primary text-foreground bg-accent" : "border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground"
478
+ ),
479
+ children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "size-4", "aria-hidden": true }),
481
+ label
482
+ ]
483
+ },
484
+ id
485
+ );
486
+ }) }),
487
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "label-mono text-muted-foreground border-t px-3 pt-3 pb-1", children: "theme" }),
488
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "pb-1", children: themes.map((t) => {
489
+ const active = t.id === skin;
490
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ role: "menuitemradio",
495
+ "aria-checked": active,
496
+ onClick: () => setSkin(t.id),
497
+ className: cn(
498
+ "hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors",
499
+ active && "text-foreground"
500
+ ),
501
+ children: [
502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 size-4 shrink-0", children: active && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "text-primary size-4", "aria-hidden": true }) }),
503
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
504
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm font-medium", children: t.label }),
505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground block text-xs leading-snug", children: t.description })
506
+ ] })
507
+ ]
508
+ }
509
+ ) }, t.id);
510
+ }) })
511
+ ]
512
+ }
513
+ )
514
+ ] });
515
+ }
384
516
  var buttonVariants = classVarianceAuthority.cva(
385
517
  "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
386
518
  {
@@ -1503,6 +1635,345 @@ var Toaster = ({ ...props }) => {
1503
1635
  }
1504
1636
  );
1505
1637
  };
1638
+ function Card({
1639
+ id,
1640
+ title,
1641
+ subtitle,
1642
+ refreshedAt,
1643
+ actions,
1644
+ children,
1645
+ className,
1646
+ tone = "default",
1647
+ expandable = true
1648
+ }) {
1649
+ const [expanded, setExpanded] = React2.useState(false);
1650
+ const toneClass = tone === "warning" ? "border-warning/60" : tone === "destructive" ? "border-destructive/60" : tone === "muted" ? "border-border opacity-90" : "border-border";
1651
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1652
+ /* @__PURE__ */ jsxRuntime.jsxs(
1653
+ "section",
1654
+ {
1655
+ id,
1656
+ className: cn("terminal flex flex-col rounded-sm border scroll-mt-16", toneClass, className),
1657
+ children: [
1658
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "terminal-title flex items-center justify-between gap-3", children: [
1659
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "terminal-dot", "aria-hidden": true }),
1661
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "terminal-dot opacity-70", "aria-hidden": true }),
1662
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "terminal-dot opacity-50", "aria-hidden": true }),
1663
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 truncate", children: title }),
1664
+ subtitle && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground/70 ml-1 truncate text-[10px] tracking-normal normal-case", children: [
1665
+ "\xB7 ",
1666
+ subtitle
1667
+ ] })
1668
+ ] }),
1669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1670
+ refreshedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/60 hidden text-[10px] tracking-normal normal-case sm:inline", children: refreshedAt }),
1671
+ actions,
1672
+ expandable && /* @__PURE__ */ jsxRuntime.jsx(
1673
+ "button",
1674
+ {
1675
+ type: "button",
1676
+ onClick: () => setExpanded(true),
1677
+ "aria-label": `expand ${title}`,
1678
+ title: "expand to fullscreen",
1679
+ className: "text-muted-foreground/70 hover:text-foreground inline-flex h-6 w-6 items-center justify-center rounded-sm",
1680
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "size-3" })
1681
+ }
1682
+ )
1683
+ ] })
1684
+ ] }),
1685
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 p-4 sm:p-5", children })
1686
+ ]
1687
+ }
1688
+ ),
1689
+ expandable && /* @__PURE__ */ jsxRuntime.jsx(
1690
+ Modal,
1691
+ {
1692
+ open: expanded,
1693
+ onOpenChange: setExpanded,
1694
+ title,
1695
+ ...subtitle ? { subtitle } : {},
1696
+ actions: refreshedAt ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/60 hidden text-[10px] tracking-normal normal-case sm:inline", children: refreshedAt }) : null,
1697
+ children
1698
+ }
1699
+ )
1700
+ ] });
1701
+ }
1702
+ function Sparkline({ data, width = 96, height = 24, area = false, className }) {
1703
+ if (data.length === 0) {
1704
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width, height, className: cn(className), "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(
1705
+ "line",
1706
+ {
1707
+ x1: 0,
1708
+ x2: width,
1709
+ y1: height / 2,
1710
+ y2: height / 2,
1711
+ stroke: "currentColor",
1712
+ strokeWidth: 1,
1713
+ opacity: 0.25
1714
+ }
1715
+ ) });
1716
+ }
1717
+ const min = Math.min(...data);
1718
+ const max = Math.max(...data);
1719
+ const range = max - min || 1;
1720
+ const step = data.length === 1 ? width : width / (data.length - 1);
1721
+ const points = data.map((v, i) => {
1722
+ const x = i * step;
1723
+ const y = height - (v - min) / range * (height - 2) - 1;
1724
+ return [x, y];
1725
+ });
1726
+ const linePath = points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`).join(" ");
1727
+ const areaPath = area ? `${linePath} L${width.toFixed(2)},${height.toFixed(2)} L0,${height.toFixed(2)} Z` : null;
1728
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1729
+ "svg",
1730
+ {
1731
+ width,
1732
+ height,
1733
+ viewBox: `0 0 ${width} ${height}`,
1734
+ className: cn("text-primary", className),
1735
+ "aria-hidden": true,
1736
+ children: [
1737
+ areaPath && /* @__PURE__ */ jsxRuntime.jsx("path", { d: areaPath, fill: "currentColor", opacity: 0.12 }),
1738
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: linePath, fill: "none", stroke: "currentColor", strokeWidth: 1.25 })
1739
+ ]
1740
+ }
1741
+ );
1742
+ }
1743
+ function StatBlock({ label, value, sub, delta, tone = "default", className }) {
1744
+ const toneClass = tone === "success" ? "text-success" : tone === "warning" ? "text-warning" : tone === "destructive" ? "text-destructive" : "text-foreground";
1745
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1", className), children: [
1746
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "label-mono text-[10px]", children: label }),
1747
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-display text-2xl leading-none", toneClass), children: value }),
1748
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
1749
+ sub && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-mono text-[11px] tracking-wide lowercase", children: sub }),
1750
+ delta && /* @__PURE__ */ jsxRuntime.jsxs(
1751
+ "span",
1752
+ {
1753
+ className: cn(
1754
+ "font-mono text-[11px] tracking-wide",
1755
+ delta.value > 0 ? "text-success" : delta.value < 0 ? "text-destructive" : "text-muted-foreground"
1756
+ ),
1757
+ children: [
1758
+ delta.value > 0 ? "\u25B2" : delta.value < 0 ? "\u25BC" : "\xB7",
1759
+ " ",
1760
+ Math.abs(delta.value).toFixed(1),
1761
+ delta.suffix ?? "%"
1762
+ ]
1763
+ }
1764
+ )
1765
+ ] })
1766
+ ] });
1767
+ }
1768
+ var ACCENT = {
1769
+ primary: "bg-primary",
1770
+ warning: "bg-warning",
1771
+ destructive: "bg-destructive",
1772
+ success: "bg-success"
1773
+ };
1774
+ function Bar({ pct, accent = "primary", label, right, className }) {
1775
+ const clamped = Math.max(0, Math.min(100, pct));
1776
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(className), children: [
1777
+ (label || right) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between font-mono text-[10.5px] tracking-widest uppercase", children: [
1778
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
1779
+ right && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground tabular-nums", children: right })
1780
+ ] }),
1781
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted mt-1.5 h-1.5 w-full overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("h-full", ACCENT[accent]), style: { width: `${clamped}%` } }) })
1782
+ ] });
1783
+ }
1784
+ var TONE = {
1785
+ success: "text-success",
1786
+ warning: "text-warning",
1787
+ destructive: "text-destructive",
1788
+ muted: "text-muted-foreground",
1789
+ primary: "text-primary",
1790
+ info: "text-info"
1791
+ };
1792
+ function StatusPill({ label, tone = "muted", icon, className }) {
1793
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1794
+ "span",
1795
+ {
1796
+ className: cn(
1797
+ "inline-flex items-center gap-1.5 font-mono text-[11px] tracking-wider lowercase",
1798
+ TONE[tone],
1799
+ className
1800
+ ),
1801
+ children: [
1802
+ icon,
1803
+ label
1804
+ ]
1805
+ }
1806
+ );
1807
+ }
1808
+
1809
+ // src/format/index.ts
1810
+ var REFERENCE_BTC_USD = 95e3;
1811
+ function formatSats(sats) {
1812
+ return new Intl.NumberFormat("en-US").format(Math.round(sats));
1813
+ }
1814
+ function formatSatsCompact(sats) {
1815
+ const n = Math.round(sats);
1816
+ if (n < 1e3) return `${n} sats`;
1817
+ const k = n / 1e3;
1818
+ return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;
1819
+ }
1820
+ function satsToUsd(sats, btcUsd = REFERENCE_BTC_USD) {
1821
+ const usd = sats / 1e8 * btcUsd;
1822
+ if (usd > 0 && usd < 0.01) return "< $0.01";
1823
+ const fractionDigits = usd >= 100 ? 0 : 2;
1824
+ return new Intl.NumberFormat("en-US", {
1825
+ style: "currency",
1826
+ currency: "USD",
1827
+ minimumFractionDigits: fractionDigits,
1828
+ maximumFractionDigits: fractionDigits
1829
+ }).format(usd);
1830
+ }
1831
+ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
1832
+ return Math.round(usd / btcUsd * 1e8);
1833
+ }
1834
+ function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
1835
+ return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
1836
+ }
1837
+ function relativeTime(when) {
1838
+ const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
1839
+ if (Number.isNaN(t)) return "";
1840
+ const diff = Date.now() - t;
1841
+ const m = Math.floor(diff / 6e4);
1842
+ if (m < 1) return "now";
1843
+ if (m < 60) return `${m}m ago`;
1844
+ const h = Math.floor(m / 60);
1845
+ if (h < 24) return `${h}h ago`;
1846
+ const d = Math.floor(h / 24);
1847
+ if (d < 30) return `${d}d ago`;
1848
+ const mo = Math.floor(d / 30);
1849
+ if (mo < 12) return `${mo}mo ago`;
1850
+ return `${Math.floor(mo / 12)}y ago`;
1851
+ }
1852
+ function shortenAddress(value, head = 8, tail = 4) {
1853
+ if (!value) return "";
1854
+ if (value.length <= head + tail + 1) return value;
1855
+ return `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
1856
+ }
1857
+ function explorerUrl(target, network = "mainnet") {
1858
+ const base = network === "mainnet" ? "https://mempool.space" : `https://mempool.space/${network}`;
1859
+ if ("address" in target) return `${base}/address/${target.address}`;
1860
+ if ("tx" in target) return `${base}/tx/${target.tx}`;
1861
+ return `${base}/block/${target.block}`;
1862
+ }
1863
+ function SatsAmount({ sats, unit = true, usd = false, btcUsd, className }) {
1864
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-mono tabular-nums", className), children: [
1865
+ formatSats(sats),
1866
+ unit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: " sats" }),
1867
+ usd && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
1868
+ " \xB7 ",
1869
+ satsToUsd(sats, btcUsd)
1870
+ ] })
1871
+ ] });
1872
+ }
1873
+ function CopyButton({
1874
+ value,
1875
+ label,
1876
+ clearAfter,
1877
+ size = "md",
1878
+ className,
1879
+ title,
1880
+ onCopied
1881
+ }) {
1882
+ const [copied, setCopied] = React2.useState(false);
1883
+ const resetRef = React2.useRef(null);
1884
+ const copy = React2.useCallback(async () => {
1885
+ try {
1886
+ await navigator.clipboard.writeText(value);
1887
+ setCopied(true);
1888
+ onCopied?.();
1889
+ if (resetRef.current) clearTimeout(resetRef.current);
1890
+ resetRef.current = setTimeout(() => setCopied(false), 1500);
1891
+ if (clearAfter && clearAfter > 0) {
1892
+ setTimeout(() => void wipeIfUnchanged(value), clearAfter * 1e3);
1893
+ }
1894
+ } catch {
1895
+ }
1896
+ }, [value, clearAfter, onCopied]);
1897
+ const iconSize = size === "sm" ? "size-3" : "size-3.5";
1898
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1899
+ "button",
1900
+ {
1901
+ type: "button",
1902
+ onClick: copy,
1903
+ "aria-label": title ?? (copied ? "copied" : "copy"),
1904
+ title: title ?? "copy",
1905
+ className: cn(
1906
+ "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 rounded-sm transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
1907
+ size === "sm" ? "text-[11px]" : "text-xs",
1908
+ className
1909
+ ),
1910
+ children: [
1911
+ copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: cn(iconSize, "text-success"), "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: iconSize, "aria-hidden": true }),
1912
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: copied ? "copied" : label })
1913
+ ]
1914
+ }
1915
+ );
1916
+ }
1917
+ async function wipeIfUnchanged(expected) {
1918
+ try {
1919
+ const current = await navigator.clipboard.readText();
1920
+ if (current !== expected) return;
1921
+ await navigator.clipboard.writeText("");
1922
+ } catch {
1923
+ try {
1924
+ await navigator.clipboard.writeText("");
1925
+ } catch {
1926
+ }
1927
+ }
1928
+ }
1929
+ function BitcoinAddress({
1930
+ address,
1931
+ head = 8,
1932
+ tail = 4,
1933
+ copyable = true,
1934
+ full = false,
1935
+ className
1936
+ }) {
1937
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("inline-flex items-center gap-1.5 font-mono text-xs", className), children: [
1938
+ /* @__PURE__ */ jsxRuntime.jsx("span", { title: address, children: full ? address : shortenAddress(address, head, tail) }),
1939
+ copyable && /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: address, size: "sm", title: "copy address" })
1940
+ ] });
1941
+ }
1942
+ function QrCode({ value, size = 200, label, className }) {
1943
+ const [svg, setSvg] = React2.useState(null);
1944
+ React2.useEffect(() => {
1945
+ let cancelled = false;
1946
+ QRCode__default.default.toString(value, {
1947
+ type: "svg",
1948
+ errorCorrectionLevel: "M",
1949
+ margin: 1,
1950
+ color: { dark: "#000000", light: "#ffffff" }
1951
+ }).then((out) => {
1952
+ if (!cancelled) setSvg(out);
1953
+ }).catch(() => {
1954
+ if (!cancelled) setSvg(null);
1955
+ });
1956
+ return () => {
1957
+ cancelled = true;
1958
+ };
1959
+ }, [value]);
1960
+ return /* @__PURE__ */ jsxRuntime.jsx(
1961
+ "div",
1962
+ {
1963
+ role: "img",
1964
+ "aria-label": label ?? `QR code for ${value.slice(0, 24)}\u2026`,
1965
+ className: "inline-block overflow-hidden rounded-md bg-white p-3 " + (className ?? ""),
1966
+ style: { width: size, height: size },
1967
+ children: svg ? /* @__PURE__ */ jsxRuntime.jsx(
1968
+ "div",
1969
+ {
1970
+ className: "size-full [&_svg]:block [&_svg]:size-full",
1971
+ dangerouslySetInnerHTML: { __html: svg }
1972
+ }
1973
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted/40 size-full animate-pulse rounded", "aria-hidden": true })
1974
+ }
1975
+ );
1976
+ }
1506
1977
 
1507
1978
  // src/components/index.ts
1508
1979
  var components_exports = {};
@@ -1521,8 +1992,12 @@ exports.AlertTitle = AlertTitle;
1521
1992
  exports.AlertWithAction = AlertWithAction;
1522
1993
  exports.AlertWithCountdown = AlertWithCountdown;
1523
1994
  exports.Badge = Badge;
1995
+ exports.Bar = Bar;
1996
+ exports.BitcoinAddress = BitcoinAddress;
1524
1997
  exports.Button = Button;
1998
+ exports.Card = Card;
1525
1999
  exports.ConfirmHost = ConfirmHost;
2000
+ exports.CopyButton = CopyButton;
1526
2001
  exports.DEFAULT_OC_THEME = DEFAULT_OC_THEME;
1527
2002
  exports.Dialog = Dialog;
1528
2003
  exports.DialogClose = DialogClose;
@@ -1540,6 +2015,7 @@ exports.Label = Label;
1540
2015
  exports.Modal = Modal;
1541
2016
  exports.OC_SKIN_COOKIE = OC_SKIN_COOKIE;
1542
2017
  exports.OC_THEMES = OC_THEMES;
2018
+ exports.OcAppearanceMenu = OcAppearanceMenu;
1543
2019
  exports.OcThemeBridge = OcThemeBridge;
1544
2020
  exports.OcThemePicker = OcThemePicker;
1545
2021
  exports.OcThemeProvider = OcThemeProvider;
@@ -1549,6 +2025,9 @@ exports.PopoverAnchor = PopoverAnchor;
1549
2025
  exports.PopoverContent = PopoverContent;
1550
2026
  exports.PopoverTrigger = PopoverTrigger;
1551
2027
  exports.PromptHost = PromptHost;
2028
+ exports.QrCode = QrCode;
2029
+ exports.REFERENCE_BTC_USD = REFERENCE_BTC_USD;
2030
+ exports.SatsAmount = SatsAmount;
1552
2031
  exports.Select = Select;
1553
2032
  exports.SelectContent = SelectContent;
1554
2033
  exports.SelectGroup = SelectGroup;
@@ -1569,6 +2048,9 @@ exports.SheetPortal = SheetPortal;
1569
2048
  exports.SheetTitle = SheetTitle;
1570
2049
  exports.SheetTrigger = SheetTrigger;
1571
2050
  exports.Skeleton = Skeleton;
2051
+ exports.Sparkline = Sparkline;
2052
+ exports.StatBlock = StatBlock;
2053
+ exports.StatusPill = StatusPill;
1572
2054
  exports.Textarea = Textarea;
1573
2055
  exports.ThemeToggle = ThemeToggle;
1574
2056
  exports.ThemeToggleLink = ThemeToggleLink;
@@ -1579,10 +2061,18 @@ exports.badgeVariants = badgeVariants;
1579
2061
  exports.buttonVariants = buttonVariants;
1580
2062
  exports.cn = cn;
1581
2063
  exports.confirm = confirm;
2064
+ exports.explorerUrl = explorerUrl;
2065
+ exports.formatSats = formatSats;
2066
+ exports.formatSatsCompact = formatSatsCompact;
1582
2067
  exports.getOcThemeInitScript = getOcThemeInitScript;
1583
2068
  exports.isKnownTheme = isKnownTheme;
2069
+ exports.priceBoth = priceBoth;
1584
2070
  exports.prompt = prompt;
2071
+ exports.relativeTime = relativeTime;
1585
2072
  exports.resolveTheme = resolveTheme;
2073
+ exports.satsToUsd = satsToUsd;
2074
+ exports.shortenAddress = shortenAddress;
2075
+ exports.usdToSats = usdToSats;
1586
2076
  exports.useOcSkin = useOcSkin;
1587
2077
  //# sourceMappingURL=index.js.map
1588
2078
  //# sourceMappingURL=index.js.map