@vespera-ui/vue 0.4.0 → 0.6.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.cjs CHANGED
@@ -22,25 +22,31 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Accordion: () => Accordion,
24
24
  Alert: () => Alert,
25
+ AreaChart: () => AreaChart,
25
26
  Avatar: () => Avatar,
26
27
  AvatarGroup: () => AvatarGroup,
27
28
  Badge: () => Badge,
28
29
  Banner: () => Banner,
30
+ BarChart: () => BarChart,
29
31
  Breadcrumb: () => Breadcrumb,
30
32
  Button: () => Button,
31
33
  Card: () => Card,
32
34
  CardHead: () => CardHead,
33
35
  Checkbox: () => Checkbox,
34
36
  CircularProgress: () => CircularProgress,
37
+ CopyButton: () => CopyButton,
35
38
  DescriptionList: () => DescriptionList,
36
39
  Divider: () => Divider,
37
40
  Donut: () => Donut,
38
41
  EmptyState: () => EmptyState,
39
42
  Field: () => Field,
40
43
  IconButton: () => IconButton,
44
+ InlineEdit: () => InlineEdit,
41
45
  Input: () => Input,
42
46
  Kbd: () => Kbd,
43
47
  NativeSelect: () => NativeSelect,
48
+ NumberStepper: () => NumberStepper,
49
+ OTPInput: () => OTPInput,
44
50
  Pagination: () => Pagination,
45
51
  Progress: () => Progress,
46
52
  Radio: () => Radio,
@@ -58,6 +64,8 @@ __export(index_exports, {
58
64
  Tag: () => Tag,
59
65
  Textarea: () => Textarea,
60
66
  Timeline: () => Timeline,
67
+ Tree: () => Tree,
68
+ niceNum: () => niceNum,
61
69
  smoothPath: () => smoothPath
62
70
  });
63
71
  module.exports = __toCommonJS(index_exports);
@@ -1270,29 +1278,488 @@ var StatCard = (0, import_vue.defineComponent)({
1270
1278
  );
1271
1279
  }
1272
1280
  });
1281
+ var ICON_CHECK = "M20 6L9 17l-5-5";
1282
+ var ICON_DOC = "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8zM14 2v6h6";
1283
+ var ICON_PENCIL = "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4z";
1284
+ var NumberStepper = (0, import_vue.defineComponent)({
1285
+ name: "VspNumberStepper",
1286
+ props: {
1287
+ modelValue: { type: Number, default: 0 },
1288
+ min: { type: Number, default: void 0 },
1289
+ max: { type: Number, default: void 0 },
1290
+ step: { type: Number, default: 1 },
1291
+ unit: { type: String, default: void 0 }
1292
+ },
1293
+ emits: ["update:modelValue"],
1294
+ setup(props, { emit }) {
1295
+ const set = (v) => {
1296
+ let n = v;
1297
+ if (props.min != null && n < props.min) n = props.min;
1298
+ if (props.max != null && n > props.max) n = props.max;
1299
+ emit("update:modelValue", n);
1300
+ };
1301
+ return () => (0, import_vue.h)("div", { class: "ui-stepper" }, [
1302
+ (0, import_vue.h)(
1303
+ "button",
1304
+ {
1305
+ type: "button",
1306
+ "aria-label": "Decrease",
1307
+ disabled: props.min != null && props.modelValue <= props.min,
1308
+ onClick: () => set(props.modelValue - props.step)
1309
+ },
1310
+ "\u2212"
1311
+ ),
1312
+ (0, import_vue.h)("span", { class: "val" }, [
1313
+ props.modelValue,
1314
+ props.unit ? (0, import_vue.h)("i", null, props.unit) : null
1315
+ ]),
1316
+ (0, import_vue.h)(
1317
+ "button",
1318
+ {
1319
+ type: "button",
1320
+ "aria-label": "Increase",
1321
+ disabled: props.max != null && props.modelValue >= props.max,
1322
+ onClick: () => set(props.modelValue + props.step)
1323
+ },
1324
+ "+"
1325
+ )
1326
+ ]);
1327
+ }
1328
+ });
1329
+ var CopyButton = (0, import_vue.defineComponent)({
1330
+ name: "VspCopyButton",
1331
+ props: {
1332
+ text: { type: String, required: true },
1333
+ label: { type: String, default: "Copy" },
1334
+ size: { type: String, default: "sm" }
1335
+ },
1336
+ setup(props) {
1337
+ const done = (0, import_vue.ref)(false);
1338
+ const copy = async () => {
1339
+ try {
1340
+ await navigator.clipboard?.writeText(props.text);
1341
+ } catch {
1342
+ }
1343
+ done.value = true;
1344
+ setTimeout(() => done.value = false, 1400);
1345
+ };
1346
+ return () => (0, import_vue.h)(
1347
+ "button",
1348
+ {
1349
+ type: "button",
1350
+ class: cx("btn", "btn-ghost", props.size === "sm" && "btn-sm"),
1351
+ onClick: copy
1352
+ },
1353
+ [
1354
+ done.value ? (0, import_vue.h)("span", { style: { color: "var(--success)", display: "inline-flex" } }, [
1355
+ svgIcon(ICON_CHECK, 15)
1356
+ ]) : svgIcon(ICON_DOC, 15),
1357
+ done.value ? "Copied" : props.label
1358
+ ]
1359
+ );
1360
+ }
1361
+ });
1362
+ var InlineEdit = (0, import_vue.defineComponent)({
1363
+ name: "VspInlineEdit",
1364
+ props: {
1365
+ value: { type: String, default: "" },
1366
+ placeholder: { type: String, default: "Empty" }
1367
+ },
1368
+ emits: ["save"],
1369
+ setup(props, { emit }) {
1370
+ const editing = (0, import_vue.ref)(false);
1371
+ const draft = (0, import_vue.ref)(props.value);
1372
+ const commit = () => {
1373
+ editing.value = false;
1374
+ if (draft.value !== props.value) emit("save", draft.value);
1375
+ };
1376
+ return () => editing.value ? (0, import_vue.h)("input", {
1377
+ class: "ui-input",
1378
+ value: draft.value,
1379
+ style: { height: "32px", maxWidth: "240px" },
1380
+ onVnodeMounted: (vn) => vn.el?.focus(),
1381
+ onInput: (e) => draft.value = e.target.value,
1382
+ onBlur: commit,
1383
+ onKeydown: (e) => {
1384
+ if (e.key === "Enter") commit();
1385
+ if (e.key === "Escape") {
1386
+ draft.value = props.value;
1387
+ editing.value = false;
1388
+ }
1389
+ }
1390
+ }) : (0, import_vue.h)(
1391
+ "span",
1392
+ {
1393
+ class: "ui-inline",
1394
+ onClick: () => {
1395
+ draft.value = props.value;
1396
+ editing.value = true;
1397
+ }
1398
+ },
1399
+ [
1400
+ (0, import_vue.h)(
1401
+ "span",
1402
+ { style: { color: props.value ? "var(--text)" : "var(--text-faint)" } },
1403
+ props.value || props.placeholder
1404
+ ),
1405
+ (0, import_vue.h)("span", { class: "pen", style: { display: "inline-flex" } }, [
1406
+ svgIcon(ICON_PENCIL, 14)
1407
+ ])
1408
+ ]
1409
+ );
1410
+ }
1411
+ });
1412
+ var svgIconClass = (d, size, cls) => (0, import_vue.h)(
1413
+ "svg",
1414
+ {
1415
+ class: cls,
1416
+ viewBox: "0 0 24 24",
1417
+ width: size,
1418
+ height: size,
1419
+ fill: "none",
1420
+ stroke: "currentColor",
1421
+ "stroke-width": 2,
1422
+ "stroke-linecap": "round",
1423
+ "stroke-linejoin": "round"
1424
+ },
1425
+ [(0, import_vue.h)("path", { d })]
1426
+ );
1427
+ var ICON_LAYERS = "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5";
1428
+ var treeNodeId = (n) => n.id ?? n.label;
1429
+ var VspTreeNode = (0, import_vue.defineComponent)({
1430
+ name: "VspTreeNode",
1431
+ props: {
1432
+ node: { type: Object, required: true },
1433
+ expanded: { type: Object, required: true },
1434
+ selected: { type: String, default: null },
1435
+ toggle: { type: Function, required: true },
1436
+ select: { type: Function, required: true }
1437
+ },
1438
+ setup(props) {
1439
+ return () => {
1440
+ const node = props.node;
1441
+ const id = treeNodeId(node);
1442
+ const kids = node.children ?? [];
1443
+ const hasKids = kids.length > 0;
1444
+ const open = props.expanded.has(id);
1445
+ return (0, import_vue.h)("div", null, [
1446
+ (0, import_vue.h)(
1447
+ "div",
1448
+ {
1449
+ class: cx("ui-tree-row", open && "open", props.selected === id && "sel"),
1450
+ onClick: () => {
1451
+ if (hasKids) props.toggle(id);
1452
+ props.select(id);
1453
+ }
1454
+ },
1455
+ [
1456
+ hasKids ? svgIconClass(ICON_PATHS.chevR, 16, "tw-chev") : (0, import_vue.h)("span", { style: { width: "16px", flexShrink: 0 } }),
1457
+ svgIconClass(hasKids ? ICON_LAYERS : ICON_DOC, 16, "tw-icon"),
1458
+ (0, import_vue.h)(
1459
+ "span",
1460
+ {
1461
+ style: {
1462
+ flex: 1,
1463
+ minWidth: 0,
1464
+ overflow: "hidden",
1465
+ textOverflow: "ellipsis",
1466
+ whiteSpace: "nowrap"
1467
+ }
1468
+ },
1469
+ node.label
1470
+ ),
1471
+ node.badge != null ? (0, import_vue.h)(
1472
+ "span",
1473
+ { class: "mono", style: { fontSize: "11px", color: "var(--text-faint)" } },
1474
+ node.badge
1475
+ ) : null
1476
+ ]
1477
+ ),
1478
+ hasKids && open ? (0, import_vue.h)(
1479
+ "div",
1480
+ { class: "ui-tree-children" },
1481
+ kids.map(
1482
+ (c, i) => (0, import_vue.h)(VspTreeNode, {
1483
+ key: i,
1484
+ node: c,
1485
+ expanded: props.expanded,
1486
+ selected: props.selected,
1487
+ toggle: props.toggle,
1488
+ select: props.select
1489
+ })
1490
+ )
1491
+ ) : null
1492
+ ]);
1493
+ };
1494
+ }
1495
+ });
1496
+ var Tree = (0, import_vue.defineComponent)({
1497
+ name: "VspTree",
1498
+ props: {
1499
+ data: { type: Array, default: () => [] },
1500
+ defaultExpanded: { type: Array, default: () => [] }
1501
+ },
1502
+ setup(props) {
1503
+ const expanded = (0, import_vue.ref)(new Set(props.defaultExpanded));
1504
+ const selected = (0, import_vue.ref)(null);
1505
+ const toggle = (id) => {
1506
+ const n = new Set(expanded.value);
1507
+ if (n.has(id)) n.delete(id);
1508
+ else n.add(id);
1509
+ expanded.value = n;
1510
+ };
1511
+ return () => (0, import_vue.h)(
1512
+ "div",
1513
+ { class: "ui-tree" },
1514
+ props.data.map(
1515
+ (n, i) => (0, import_vue.h)(VspTreeNode, {
1516
+ key: i,
1517
+ node: n,
1518
+ expanded: expanded.value,
1519
+ selected: selected.value,
1520
+ toggle,
1521
+ select: (id) => selected.value = id
1522
+ })
1523
+ )
1524
+ );
1525
+ }
1526
+ });
1527
+ var OTPInput = (0, import_vue.defineComponent)({
1528
+ name: "VspOTPInput",
1529
+ props: {
1530
+ length: { type: Number, default: 6 },
1531
+ modelValue: { type: String, default: "" }
1532
+ },
1533
+ emits: ["update:modelValue"],
1534
+ setup(props, { emit }) {
1535
+ const refs = [];
1536
+ const set = (i, ch) => {
1537
+ const chars = Array.from({ length: props.length }, (_, k) => props.modelValue[k] ?? "");
1538
+ chars[i] = ch.slice(-1);
1539
+ emit("update:modelValue", chars.join(""));
1540
+ if (ch && i < props.length - 1) refs[i + 1]?.focus();
1541
+ };
1542
+ const onKey = (i, e) => {
1543
+ if (e.key === "Backspace" && !props.modelValue[i] && i > 0) refs[i - 1]?.focus();
1544
+ };
1545
+ return () => (0, import_vue.h)(
1546
+ "div",
1547
+ { class: "ui-otp" },
1548
+ Array.from(
1549
+ { length: props.length },
1550
+ (_, i) => (0, import_vue.h)("input", {
1551
+ key: i,
1552
+ ref: (el) => {
1553
+ refs[i] = el;
1554
+ },
1555
+ inputmode: "numeric",
1556
+ maxlength: 1,
1557
+ value: props.modelValue[i] ?? "",
1558
+ onInput: (e) => set(i, e.target.value.replace(/\D/g, "")),
1559
+ onKeydown: (e) => onKey(i, e)
1560
+ })
1561
+ )
1562
+ );
1563
+ }
1564
+ });
1565
+ function niceNum(n) {
1566
+ if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1) + "M";
1567
+ if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(n % 1e3 === 0 ? 0 : 1) + "k";
1568
+ return String(n);
1569
+ }
1570
+ var AreaChart = (0, import_vue.defineComponent)({
1571
+ name: "VspAreaChart",
1572
+ props: {
1573
+ series: { type: Array, default: () => [] },
1574
+ labels: { type: Array, default: void 0 },
1575
+ width: { type: Number, default: 760 },
1576
+ height: { type: Number, default: 260 },
1577
+ color: { type: String, default: "var(--accent)" },
1578
+ color2: { type: String, default: "var(--accent-2)" },
1579
+ dual: Boolean
1580
+ },
1581
+ setup(props) {
1582
+ const gid = "ac" + (0, import_vue.useId)().replace(/[^a-zA-Z0-9]/g, "");
1583
+ const txt = (x, y, anchor, val) => (0, import_vue.h)(
1584
+ "text",
1585
+ {
1586
+ x,
1587
+ y,
1588
+ "text-anchor": anchor,
1589
+ "font-size": "10",
1590
+ fill: "var(--text-faint)",
1591
+ "font-family": "var(--font-mono)"
1592
+ },
1593
+ val
1594
+ );
1595
+ return () => {
1596
+ const w = props.width;
1597
+ const height = props.height;
1598
+ const padL = 38;
1599
+ const padB = 26;
1600
+ const padT = 12;
1601
+ const padR = 8;
1602
+ const innerW = Math.max(10, w - padL - padR);
1603
+ const innerH = height - padB - padT;
1604
+ const s0 = props.series[0] ?? [];
1605
+ const s1 = props.series[1];
1606
+ const all = props.dual && s1 ? [...s0, ...s1] : s0;
1607
+ const max = Math.max(...all, 0) * 1.12;
1608
+ const rng = max || 1;
1609
+ const sx = (i, len) => padL + i / Math.max(1, len - 1) * innerW;
1610
+ const sy = (v) => padT + innerH - v / rng * innerH;
1611
+ const mkPts = (arr) => arr.map((v, i) => [sx(i, arr.length), sy(v)]);
1612
+ const lines = (props.dual && s1 ? [s0, s1] : [s0]).map(mkPts);
1613
+ const yTicks = 4;
1614
+ const grid = Array.from({ length: yTicks + 1 }, (_, i) => {
1615
+ const y = sy(max / yTicks * i);
1616
+ return (0, import_vue.h)("g", { key: "g" + i }, [
1617
+ (0, import_vue.h)("line", {
1618
+ x1: padL,
1619
+ x2: w - padR,
1620
+ y1: y,
1621
+ y2: y,
1622
+ stroke: "var(--grid-line)",
1623
+ "stroke-width": "1"
1624
+ }),
1625
+ txt(padL - 8, y + 3.5, "end", niceNum(Math.round(max / yTicks * i)))
1626
+ ]);
1627
+ });
1628
+ const lbls = props.labels ? props.labels.map(
1629
+ (lb, i) => i % Math.ceil(props.labels.length / 7) === 0 ? txt(sx(i, props.labels.length), height - 8, "middle", lb) : null
1630
+ ) : [];
1631
+ const lineEls = lines.map((pts, li) => {
1632
+ const stroke = li === 0 ? props.color : props.color2;
1633
+ const lastPt = pts[pts.length - 1];
1634
+ const firstPt = pts[0];
1635
+ return (0, import_vue.h)("g", { key: "ln" + li }, [
1636
+ li === 0 && firstPt && lastPt ? (0, import_vue.h)("path", {
1637
+ d: `${smoothPath(pts)} L ${lastPt[0]} ${padT + innerH} L ${firstPt[0]} ${padT + innerH} Z`,
1638
+ fill: `url(#${gid})`
1639
+ }) : null,
1640
+ (0, import_vue.h)("path", {
1641
+ d: smoothPath(pts),
1642
+ fill: "none",
1643
+ stroke,
1644
+ "stroke-width": "2.4",
1645
+ "stroke-linecap": "round",
1646
+ "stroke-dasharray": li === 1 ? "5 5" : void 0,
1647
+ style: { opacity: li === 1 ? 0.7 : 1 }
1648
+ })
1649
+ ]);
1650
+ });
1651
+ return (0, import_vue.h)("svg", { width: w, height, style: { display: "block" } }, [
1652
+ (0, import_vue.h)("defs", null, [
1653
+ (0, import_vue.h)("linearGradient", { id: gid, x1: "0", x2: "0", y1: "0", y2: "1" }, [
1654
+ (0, import_vue.h)("stop", { offset: "0", "stop-color": props.color, "stop-opacity": "0.22" }),
1655
+ (0, import_vue.h)("stop", { offset: "1", "stop-color": props.color, "stop-opacity": "0" })
1656
+ ])
1657
+ ]),
1658
+ ...grid,
1659
+ ...lbls,
1660
+ ...lineEls
1661
+ ]);
1662
+ };
1663
+ }
1664
+ });
1665
+ var BarChart = (0, import_vue.defineComponent)({
1666
+ name: "VspBarChart",
1667
+ props: {
1668
+ data: { type: Array, default: () => [] },
1669
+ labels: { type: Array, default: void 0 },
1670
+ width: { type: Number, default: 620 },
1671
+ height: { type: Number, default: 240 },
1672
+ color: { type: String, default: "var(--accent)" }
1673
+ },
1674
+ setup(props) {
1675
+ return () => {
1676
+ const w = props.width;
1677
+ const height = props.height;
1678
+ const padL = 34;
1679
+ const padB = 26;
1680
+ const padT = 10;
1681
+ const innerW = Math.max(10, w - padL - 8);
1682
+ const innerH = height - padB - padT;
1683
+ const max = Math.max(...props.data, 0) * 1.15 || 1;
1684
+ const bw = innerW / (props.data.length || 1);
1685
+ const grid = [0, 0.5, 1].map((f, i) => {
1686
+ const y = padT + innerH - f * innerH;
1687
+ return (0, import_vue.h)("g", { key: "g" + i }, [
1688
+ (0, import_vue.h)("line", { x1: padL, x2: w - 8, y1: y, y2: y, stroke: "var(--grid-line)" }),
1689
+ (0, import_vue.h)(
1690
+ "text",
1691
+ {
1692
+ x: padL - 8,
1693
+ y: y + 3.5,
1694
+ "text-anchor": "end",
1695
+ "font-size": "10",
1696
+ fill: "var(--text-faint)",
1697
+ "font-family": "var(--font-mono)"
1698
+ },
1699
+ niceNum(Math.round(max * f))
1700
+ )
1701
+ ]);
1702
+ });
1703
+ const bars = props.data.map((v, i) => {
1704
+ const bh = v / max * innerH;
1705
+ const x = padL + i * bw + bw * 0.18;
1706
+ const bwi = bw * 0.64;
1707
+ return (0, import_vue.h)("g", { key: "b" + i }, [
1708
+ (0, import_vue.h)("rect", {
1709
+ x,
1710
+ y: padT + innerH - bh,
1711
+ width: bwi,
1712
+ height: bh,
1713
+ rx: "4",
1714
+ fill: `color-mix(in oklab, ${props.color} 78%, transparent)`
1715
+ }),
1716
+ props.labels?.[i] != null ? (0, import_vue.h)(
1717
+ "text",
1718
+ {
1719
+ x: x + bwi / 2,
1720
+ y: height - 8,
1721
+ "text-anchor": "middle",
1722
+ "font-size": "10",
1723
+ fill: "var(--text-faint)",
1724
+ "font-family": "var(--font-mono)"
1725
+ },
1726
+ props.labels[i]
1727
+ ) : null
1728
+ ]);
1729
+ });
1730
+ return (0, import_vue.h)("svg", { width: w, height, style: { display: "block" } }, [...grid, ...bars]);
1731
+ };
1732
+ }
1733
+ });
1273
1734
  // Annotate the CommonJS export names for ESM import in node:
1274
1735
  0 && (module.exports = {
1275
1736
  Accordion,
1276
1737
  Alert,
1738
+ AreaChart,
1277
1739
  Avatar,
1278
1740
  AvatarGroup,
1279
1741
  Badge,
1280
1742
  Banner,
1743
+ BarChart,
1281
1744
  Breadcrumb,
1282
1745
  Button,
1283
1746
  Card,
1284
1747
  CardHead,
1285
1748
  Checkbox,
1286
1749
  CircularProgress,
1750
+ CopyButton,
1287
1751
  DescriptionList,
1288
1752
  Divider,
1289
1753
  Donut,
1290
1754
  EmptyState,
1291
1755
  Field,
1292
1756
  IconButton,
1757
+ InlineEdit,
1293
1758
  Input,
1294
1759
  Kbd,
1295
1760
  NativeSelect,
1761
+ NumberStepper,
1762
+ OTPInput,
1296
1763
  Pagination,
1297
1764
  Progress,
1298
1765
  Radio,
@@ -1310,6 +1777,8 @@ var StatCard = (0, import_vue.defineComponent)({
1310
1777
  Tag,
1311
1778
  Textarea,
1312
1779
  Timeline,
1780
+ Tree,
1781
+ niceNum,
1313
1782
  smoothPath
1314
1783
  });
1315
1784
  //# sourceMappingURL=index.cjs.map