@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.
@@ -2,9 +2,9 @@ import { Slot } from '@radix-ui/react-slot';
2
2
  import { cva } from 'class-variance-authority';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import * as LabelPrimitive from '@radix-ui/react-label';
7
- import { ChevronDown, Sun, Moon, X, XIcon, AlertTriangle, Pencil, ChevronLeft, ChevronRight, ChevronDownIcon, CheckIcon, ChevronUpIcon } from 'lucide-react';
7
+ import { ChevronDown, Sun, Moon, X, XIcon, AlertTriangle, Pencil, ChevronLeft, ChevronRight, ChevronDownIcon, CheckIcon, ChevronUpIcon, Maximize2, Check, Copy } from 'lucide-react';
8
8
  import { useTheme } from 'next-themes';
9
9
  import * as React2 from 'react';
10
10
  import { useState, useEffect, useRef, useCallback } from 'react';
@@ -14,6 +14,7 @@ import * as PopoverPrimitive from '@radix-ui/react-popover';
14
14
  import * as AccordionPrimitive from '@radix-ui/react-accordion';
15
15
  import * as SelectPrimitive from '@radix-ui/react-select';
16
16
  import { Toaster as Toaster$1 } from 'sonner';
17
+ import QRCode from 'qrcode';
17
18
 
18
19
  // src/primitives/button.tsx
19
20
  function cn(...inputs) {
@@ -1141,7 +1142,313 @@ var Toaster = ({ ...props }) => {
1141
1142
  }
1142
1143
  );
1143
1144
  };
1145
+ function Card({
1146
+ id,
1147
+ title,
1148
+ subtitle,
1149
+ refreshedAt,
1150
+ actions,
1151
+ children,
1152
+ className,
1153
+ tone = "default",
1154
+ expandable = true
1155
+ }) {
1156
+ const [expanded, setExpanded] = useState(false);
1157
+ const toneClass = tone === "warning" ? "border-warning/60" : tone === "destructive" ? "border-destructive/60" : tone === "muted" ? "border-border opacity-90" : "border-border";
1158
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1159
+ /* @__PURE__ */ jsxs(
1160
+ "section",
1161
+ {
1162
+ id,
1163
+ className: cn("terminal flex flex-col rounded-sm border scroll-mt-16", toneClass, className),
1164
+ children: [
1165
+ /* @__PURE__ */ jsxs("div", { className: "terminal-title flex items-center justify-between gap-3", children: [
1166
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1167
+ /* @__PURE__ */ jsx("span", { className: "terminal-dot", "aria-hidden": true }),
1168
+ /* @__PURE__ */ jsx("span", { className: "terminal-dot opacity-70", "aria-hidden": true }),
1169
+ /* @__PURE__ */ jsx("span", { className: "terminal-dot opacity-50", "aria-hidden": true }),
1170
+ /* @__PURE__ */ jsx("span", { className: "ml-2 truncate", children: title }),
1171
+ subtitle && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 ml-1 truncate text-[10px] tracking-normal normal-case", children: [
1172
+ "\xB7 ",
1173
+ subtitle
1174
+ ] })
1175
+ ] }),
1176
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1177
+ refreshedAt && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 hidden text-[10px] tracking-normal normal-case sm:inline", children: refreshedAt }),
1178
+ actions,
1179
+ expandable && /* @__PURE__ */ jsx(
1180
+ "button",
1181
+ {
1182
+ type: "button",
1183
+ onClick: () => setExpanded(true),
1184
+ "aria-label": `expand ${title}`,
1185
+ title: "expand to fullscreen",
1186
+ className: "text-muted-foreground/70 hover:text-foreground inline-flex h-6 w-6 items-center justify-center rounded-sm",
1187
+ children: /* @__PURE__ */ jsx(Maximize2, { className: "size-3" })
1188
+ }
1189
+ )
1190
+ ] })
1191
+ ] }),
1192
+ /* @__PURE__ */ jsx("div", { className: "flex-1 p-4 sm:p-5", children })
1193
+ ]
1194
+ }
1195
+ ),
1196
+ expandable && /* @__PURE__ */ jsx(
1197
+ Modal,
1198
+ {
1199
+ open: expanded,
1200
+ onOpenChange: setExpanded,
1201
+ title,
1202
+ ...subtitle ? { subtitle } : {},
1203
+ actions: refreshedAt ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 hidden text-[10px] tracking-normal normal-case sm:inline", children: refreshedAt }) : null,
1204
+ children
1205
+ }
1206
+ )
1207
+ ] });
1208
+ }
1209
+ function Sparkline({ data, width = 96, height = 24, area = false, className }) {
1210
+ if (data.length === 0) {
1211
+ return /* @__PURE__ */ jsx("svg", { width, height, className: cn(className), "aria-hidden": true, children: /* @__PURE__ */ jsx(
1212
+ "line",
1213
+ {
1214
+ x1: 0,
1215
+ x2: width,
1216
+ y1: height / 2,
1217
+ y2: height / 2,
1218
+ stroke: "currentColor",
1219
+ strokeWidth: 1,
1220
+ opacity: 0.25
1221
+ }
1222
+ ) });
1223
+ }
1224
+ const min = Math.min(...data);
1225
+ const max = Math.max(...data);
1226
+ const range = max - min || 1;
1227
+ const step = data.length === 1 ? width : width / (data.length - 1);
1228
+ const points = data.map((v, i) => {
1229
+ const x = i * step;
1230
+ const y = height - (v - min) / range * (height - 2) - 1;
1231
+ return [x, y];
1232
+ });
1233
+ const linePath = points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`).join(" ");
1234
+ const areaPath = area ? `${linePath} L${width.toFixed(2)},${height.toFixed(2)} L0,${height.toFixed(2)} Z` : null;
1235
+ return /* @__PURE__ */ jsxs(
1236
+ "svg",
1237
+ {
1238
+ width,
1239
+ height,
1240
+ viewBox: `0 0 ${width} ${height}`,
1241
+ className: cn("text-primary", className),
1242
+ "aria-hidden": true,
1243
+ children: [
1244
+ areaPath && /* @__PURE__ */ jsx("path", { d: areaPath, fill: "currentColor", opacity: 0.12 }),
1245
+ /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: "currentColor", strokeWidth: 1.25 })
1246
+ ]
1247
+ }
1248
+ );
1249
+ }
1250
+ function StatBlock({ label, value, sub, delta, tone = "default", className }) {
1251
+ const toneClass = tone === "success" ? "text-success" : tone === "warning" ? "text-warning" : tone === "destructive" ? "text-destructive" : "text-foreground";
1252
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1", className), children: [
1253
+ /* @__PURE__ */ jsx("span", { className: "label-mono text-[10px]", children: label }),
1254
+ /* @__PURE__ */ jsx("span", { className: cn("font-display text-2xl leading-none", toneClass), children: value }),
1255
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
1256
+ sub && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[11px] tracking-wide lowercase", children: sub }),
1257
+ delta && /* @__PURE__ */ jsxs(
1258
+ "span",
1259
+ {
1260
+ className: cn(
1261
+ "font-mono text-[11px] tracking-wide",
1262
+ delta.value > 0 ? "text-success" : delta.value < 0 ? "text-destructive" : "text-muted-foreground"
1263
+ ),
1264
+ children: [
1265
+ delta.value > 0 ? "\u25B2" : delta.value < 0 ? "\u25BC" : "\xB7",
1266
+ " ",
1267
+ Math.abs(delta.value).toFixed(1),
1268
+ delta.suffix ?? "%"
1269
+ ]
1270
+ }
1271
+ )
1272
+ ] })
1273
+ ] });
1274
+ }
1275
+ var ACCENT = {
1276
+ primary: "bg-primary",
1277
+ warning: "bg-warning",
1278
+ destructive: "bg-destructive",
1279
+ success: "bg-success"
1280
+ };
1281
+ function Bar({ pct, accent = "primary", label, right, className }) {
1282
+ const clamped = Math.max(0, Math.min(100, pct));
1283
+ return /* @__PURE__ */ jsxs("div", { className: cn(className), children: [
1284
+ (label || right) && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between font-mono text-[10.5px] tracking-widest uppercase", children: [
1285
+ label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
1286
+ right && /* @__PURE__ */ jsx("span", { className: "text-foreground tabular-nums", children: right })
1287
+ ] }),
1288
+ /* @__PURE__ */ jsx("div", { className: "bg-muted mt-1.5 h-1.5 w-full overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: cn("h-full", ACCENT[accent]), style: { width: `${clamped}%` } }) })
1289
+ ] });
1290
+ }
1291
+ var TONE = {
1292
+ success: "text-success",
1293
+ warning: "text-warning",
1294
+ destructive: "text-destructive",
1295
+ muted: "text-muted-foreground",
1296
+ primary: "text-primary",
1297
+ info: "text-info"
1298
+ };
1299
+ function StatusPill({ label, tone = "muted", icon, className }) {
1300
+ return /* @__PURE__ */ jsxs(
1301
+ "span",
1302
+ {
1303
+ className: cn(
1304
+ "inline-flex items-center gap-1.5 font-mono text-[11px] tracking-wider lowercase",
1305
+ TONE[tone],
1306
+ className
1307
+ ),
1308
+ children: [
1309
+ icon,
1310
+ label
1311
+ ]
1312
+ }
1313
+ );
1314
+ }
1315
+
1316
+ // src/format/index.ts
1317
+ var REFERENCE_BTC_USD = 95e3;
1318
+ function formatSats(sats) {
1319
+ return new Intl.NumberFormat("en-US").format(Math.round(sats));
1320
+ }
1321
+ function satsToUsd(sats, btcUsd = REFERENCE_BTC_USD) {
1322
+ const usd = sats / 1e8 * btcUsd;
1323
+ if (usd > 0 && usd < 0.01) return "< $0.01";
1324
+ const fractionDigits = usd >= 100 ? 0 : 2;
1325
+ return new Intl.NumberFormat("en-US", {
1326
+ style: "currency",
1327
+ currency: "USD",
1328
+ minimumFractionDigits: fractionDigits,
1329
+ maximumFractionDigits: fractionDigits
1330
+ }).format(usd);
1331
+ }
1332
+ function shortenAddress(value, head = 8, tail = 4) {
1333
+ if (!value) return "";
1334
+ if (value.length <= head + tail + 1) return value;
1335
+ return `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
1336
+ }
1337
+ function SatsAmount({ sats, unit = true, usd = false, btcUsd, className }) {
1338
+ return /* @__PURE__ */ jsxs("span", { className: cn("font-mono tabular-nums", className), children: [
1339
+ formatSats(sats),
1340
+ unit && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: " sats" }),
1341
+ usd && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1342
+ " \xB7 ",
1343
+ satsToUsd(sats, btcUsd)
1344
+ ] })
1345
+ ] });
1346
+ }
1347
+ function CopyButton({
1348
+ value,
1349
+ label,
1350
+ clearAfter,
1351
+ size = "md",
1352
+ className,
1353
+ title,
1354
+ onCopied
1355
+ }) {
1356
+ const [copied, setCopied] = useState(false);
1357
+ const resetRef = useRef(null);
1358
+ const copy = useCallback(async () => {
1359
+ try {
1360
+ await navigator.clipboard.writeText(value);
1361
+ setCopied(true);
1362
+ onCopied?.();
1363
+ if (resetRef.current) clearTimeout(resetRef.current);
1364
+ resetRef.current = setTimeout(() => setCopied(false), 1500);
1365
+ if (clearAfter && clearAfter > 0) {
1366
+ setTimeout(() => void wipeIfUnchanged(value), clearAfter * 1e3);
1367
+ }
1368
+ } catch {
1369
+ }
1370
+ }, [value, clearAfter, onCopied]);
1371
+ const iconSize = size === "sm" ? "size-3" : "size-3.5";
1372
+ return /* @__PURE__ */ jsxs(
1373
+ "button",
1374
+ {
1375
+ type: "button",
1376
+ onClick: copy,
1377
+ "aria-label": title ?? (copied ? "copied" : "copy"),
1378
+ title: title ?? "copy",
1379
+ className: cn(
1380
+ "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",
1381
+ size === "sm" ? "text-[11px]" : "text-xs",
1382
+ className
1383
+ ),
1384
+ children: [
1385
+ copied ? /* @__PURE__ */ jsx(Check, { className: cn(iconSize, "text-success"), "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: iconSize, "aria-hidden": true }),
1386
+ label && /* @__PURE__ */ jsx("span", { className: "font-mono", children: copied ? "copied" : label })
1387
+ ]
1388
+ }
1389
+ );
1390
+ }
1391
+ async function wipeIfUnchanged(expected) {
1392
+ try {
1393
+ const current = await navigator.clipboard.readText();
1394
+ if (current !== expected) return;
1395
+ await navigator.clipboard.writeText("");
1396
+ } catch {
1397
+ try {
1398
+ await navigator.clipboard.writeText("");
1399
+ } catch {
1400
+ }
1401
+ }
1402
+ }
1403
+ function BitcoinAddress({
1404
+ address,
1405
+ head = 8,
1406
+ tail = 4,
1407
+ copyable = true,
1408
+ full = false,
1409
+ className
1410
+ }) {
1411
+ return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1.5 font-mono text-xs", className), children: [
1412
+ /* @__PURE__ */ jsx("span", { title: address, children: full ? address : shortenAddress(address, head, tail) }),
1413
+ copyable && /* @__PURE__ */ jsx(CopyButton, { value: address, size: "sm", title: "copy address" })
1414
+ ] });
1415
+ }
1416
+ function QrCode({ value, size = 200, label, className }) {
1417
+ const [svg, setSvg] = useState(null);
1418
+ useEffect(() => {
1419
+ let cancelled = false;
1420
+ QRCode.toString(value, {
1421
+ type: "svg",
1422
+ errorCorrectionLevel: "M",
1423
+ margin: 1,
1424
+ color: { dark: "#000000", light: "#ffffff" }
1425
+ }).then((out) => {
1426
+ if (!cancelled) setSvg(out);
1427
+ }).catch(() => {
1428
+ if (!cancelled) setSvg(null);
1429
+ });
1430
+ return () => {
1431
+ cancelled = true;
1432
+ };
1433
+ }, [value]);
1434
+ return /* @__PURE__ */ jsx(
1435
+ "div",
1436
+ {
1437
+ role: "img",
1438
+ "aria-label": label ?? `QR code for ${value.slice(0, 24)}\u2026`,
1439
+ className: "inline-block overflow-hidden rounded-md bg-white p-3 " + (className ?? ""),
1440
+ style: { width: size, height: size },
1441
+ children: svg ? /* @__PURE__ */ jsx(
1442
+ "div",
1443
+ {
1444
+ className: "size-full [&_svg]:block [&_svg]:size-full",
1445
+ dangerouslySetInnerHTML: { __html: svg }
1446
+ }
1447
+ ) : /* @__PURE__ */ jsx("div", { className: "bg-muted/40 size-full animate-pulse rounded", "aria-hidden": true })
1448
+ }
1449
+ );
1450
+ }
1144
1451
 
1145
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithCountdown, Badge, Button, ConfirmHost, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, Pagination, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetContent, SheetDescription, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, badgeVariants, buttonVariants, confirm, prompt };
1452
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithCountdown, Badge, Bar, BitcoinAddress, Button, Card, ConfirmHost, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, Pagination, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, QrCode, SatsAmount, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetContent, SheetDescription, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, Sparkline, StatBlock, StatusPill, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, badgeVariants, buttonVariants, confirm, prompt };
1146
1453
  //# sourceMappingURL=primitives.mjs.map
1147
1454
  //# sourceMappingURL=primitives.mjs.map