@vespera-ui/vue 0.1.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.cjs CHANGED
@@ -20,21 +20,45 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ Accordion: () => Accordion,
23
24
  Alert: () => Alert,
25
+ Avatar: () => Avatar,
26
+ AvatarGroup: () => AvatarGroup,
24
27
  Badge: () => Badge,
28
+ Banner: () => Banner,
29
+ Breadcrumb: () => Breadcrumb,
25
30
  Button: () => Button,
26
31
  Card: () => Card,
27
32
  CardHead: () => CardHead,
28
33
  Checkbox: () => Checkbox,
34
+ CircularProgress: () => CircularProgress,
35
+ DescriptionList: () => DescriptionList,
29
36
  Divider: () => Divider,
37
+ Donut: () => Donut,
38
+ EmptyState: () => EmptyState,
30
39
  Field: () => Field,
31
40
  IconButton: () => IconButton,
32
41
  Input: () => Input,
33
42
  Kbd: () => Kbd,
43
+ NativeSelect: () => NativeSelect,
44
+ Pagination: () => Pagination,
45
+ Progress: () => Progress,
46
+ Radio: () => Radio,
47
+ RadioGroup: () => RadioGroup,
48
+ Segmented: () => Segmented,
49
+ Skeleton: () => Skeleton,
50
+ Slider: () => Slider,
51
+ Sparkline: () => Sparkline,
34
52
  Spinner: () => Spinner,
53
+ Stat: () => Stat,
54
+ StatCard: () => StatCard,
55
+ Stepper: () => Stepper,
35
56
  Switch: () => Switch,
57
+ Tabs: () => Tabs,
36
58
  Tag: () => Tag,
37
- Textarea: () => Textarea
59
+ Textarea: () => Textarea,
60
+ Timeline: () => Timeline,
61
+ smoothPath: () => smoothPath
38
62
  });
39
63
  module.exports = __toCommonJS(index_exports);
40
64
  var import_vue = require("vue");
@@ -251,22 +275,1041 @@ var Checkbox = (0, import_vue.defineComponent)({
251
275
  );
252
276
  }
253
277
  });
278
+ var optValue = (o) => typeof o === "string" ? o : o.value;
279
+ var optLabel = (o) => typeof o === "string" ? o : o.label;
280
+ var Radio = (0, import_vue.defineComponent)({
281
+ name: "VspRadio",
282
+ props: {
283
+ checked: Boolean,
284
+ label: { type: String, default: void 0 },
285
+ sub: { type: String, default: void 0 }
286
+ },
287
+ emits: ["select"],
288
+ setup(props, { emit }) {
289
+ return () => (0, import_vue.h)(
290
+ "label",
291
+ {
292
+ class: "ui-opt",
293
+ onClick: (e) => {
294
+ e.preventDefault();
295
+ emit("select");
296
+ }
297
+ },
298
+ [
299
+ (0, import_vue.h)("span", { class: cx("ui-radio-dot", props.checked && "on") }),
300
+ (0, import_vue.h)("span", null, [
301
+ (0, import_vue.h)("span", null, props.label),
302
+ props.sub ? (0, import_vue.h)("span", { class: "ui-opt-sub" }, props.sub) : null
303
+ ])
304
+ ]
305
+ );
306
+ }
307
+ });
308
+ var RadioGroup = (0, import_vue.defineComponent)({
309
+ name: "VspRadioGroup",
310
+ props: {
311
+ modelValue: { type: String, default: void 0 },
312
+ options: { type: Array, default: () => [] }
313
+ },
314
+ emits: ["update:modelValue"],
315
+ setup(props, { emit }) {
316
+ return () => (0, import_vue.h)(
317
+ "div",
318
+ { style: { display: "flex", flexDirection: "column", gap: "12px" } },
319
+ props.options.map(
320
+ (o) => (0, import_vue.h)(Radio, {
321
+ key: optValue(o),
322
+ label: optLabel(o),
323
+ sub: typeof o === "object" ? o.sub : void 0,
324
+ checked: props.modelValue === optValue(o),
325
+ onSelect: () => emit("update:modelValue", optValue(o))
326
+ })
327
+ )
328
+ );
329
+ }
330
+ });
331
+ var Slider = (0, import_vue.defineComponent)({
332
+ name: "VspSlider",
333
+ props: {
334
+ modelValue: { type: Number, default: 0 },
335
+ min: { type: Number, default: 0 },
336
+ max: { type: Number, default: 100 },
337
+ step: { type: Number, default: 1 }
338
+ },
339
+ emits: ["update:modelValue"],
340
+ setup(props, { emit }) {
341
+ return () => (0, import_vue.h)("input", {
342
+ type: "range",
343
+ class: "ui-slider",
344
+ value: props.modelValue,
345
+ min: props.min,
346
+ max: props.max,
347
+ step: props.step,
348
+ onInput: (e) => emit("update:modelValue", Number(e.target.value))
349
+ });
350
+ }
351
+ });
352
+ var NativeSelect = (0, import_vue.defineComponent)({
353
+ name: "VspNativeSelect",
354
+ props: {
355
+ modelValue: { type: String, default: void 0 },
356
+ options: { type: Array, default: () => [] }
357
+ },
358
+ emits: ["update:modelValue"],
359
+ setup(props, { emit, attrs }) {
360
+ return () => (0, import_vue.h)(
361
+ "select",
362
+ {
363
+ class: "ui-select",
364
+ value: props.modelValue,
365
+ onChange: (e) => emit("update:modelValue", e.target.value),
366
+ ...attrs
367
+ },
368
+ props.options.map(
369
+ (o) => (0, import_vue.h)("option", { key: optValue(o), value: optValue(o) }, optLabel(o))
370
+ )
371
+ );
372
+ }
373
+ });
374
+ var px = (v) => typeof v === "number" ? `${v}px` : v;
375
+ var Progress = (0, import_vue.defineComponent)({
376
+ name: "VspProgress",
377
+ props: {
378
+ value: { type: Number, default: 0 },
379
+ tone: { type: String, default: void 0 },
380
+ height: { type: Number, default: 6 }
381
+ },
382
+ setup(props) {
383
+ return () => (0, import_vue.h)("div", { class: "meter", style: { height: px(props.height) } }, [
384
+ (0, import_vue.h)("i", {
385
+ style: {
386
+ width: `${Math.min(100, props.value)}%`,
387
+ background: props.tone,
388
+ transition: "width .3s"
389
+ }
390
+ })
391
+ ]);
392
+ }
393
+ });
394
+ var Skeleton = (0, import_vue.defineComponent)({
395
+ name: "VspSkeleton",
396
+ props: {
397
+ w: { type: [String, Number], default: "100%" },
398
+ h: { type: [String, Number], default: 12 },
399
+ r: { type: Number, default: 7 }
400
+ },
401
+ setup(props) {
402
+ return () => (0, import_vue.h)("div", {
403
+ class: "skel",
404
+ style: { width: px(props.w), height: px(props.h), borderRadius: px(props.r) }
405
+ });
406
+ }
407
+ });
408
+ var initialsOf = (name) => name.split(" ").map((s) => s.charAt(0)).slice(0, 2).join("").toUpperCase();
409
+ var Avatar = (0, import_vue.defineComponent)({
410
+ name: "VspAvatar",
411
+ props: {
412
+ name: { type: String, required: true },
413
+ hue: { type: Number, default: 0 },
414
+ size: { type: Number, default: 34 }
415
+ },
416
+ setup(props) {
417
+ return () => (0, import_vue.h)(
418
+ "span",
419
+ {
420
+ class: "vsp-avatar",
421
+ style: {
422
+ width: px(props.size),
423
+ height: px(props.size),
424
+ fontSize: px(props.size * 0.38),
425
+ background: `linear-gradient(140deg, oklch(0.62 0.16 ${props.hue}), oklch(0.55 0.17 ${(props.hue + 50) % 360}))`
426
+ }
427
+ },
428
+ initialsOf(props.name)
429
+ );
430
+ }
431
+ });
432
+ var AvatarGroup = (0, import_vue.defineComponent)({
433
+ name: "VspAvatarGroup",
434
+ props: {
435
+ people: { type: Array, default: () => [] },
436
+ max: { type: Number, default: 4 },
437
+ size: { type: Number, default: 32 }
438
+ },
439
+ setup(props) {
440
+ return () => {
441
+ const shown = props.people.slice(0, props.max);
442
+ const extra = props.people.length - shown.length;
443
+ return (0, import_vue.h)("div", { style: { display: "flex", alignItems: "center" } }, [
444
+ ...shown.map(
445
+ (p, i) => (0, import_vue.h)(
446
+ "span",
447
+ {
448
+ key: i,
449
+ style: {
450
+ marginLeft: i ? "-10px" : "0",
451
+ border: "2px solid var(--surface-1)",
452
+ borderRadius: "50%",
453
+ position: "relative",
454
+ zIndex: shown.length - i
455
+ }
456
+ },
457
+ [(0, import_vue.h)(Avatar, { name: p.name, hue: p.hue ?? 0, size: props.size })]
458
+ )
459
+ ),
460
+ extra > 0 ? (0, import_vue.h)(
461
+ "span",
462
+ {
463
+ style: {
464
+ marginLeft: "-10px",
465
+ width: px(props.size),
466
+ height: px(props.size),
467
+ borderRadius: "50%",
468
+ display: "grid",
469
+ placeItems: "center",
470
+ background: "var(--surface-3)",
471
+ border: "2px solid var(--surface-1)",
472
+ fontSize: px(props.size * 0.34),
473
+ fontWeight: 700,
474
+ color: "var(--text-dim)"
475
+ }
476
+ },
477
+ `+${extra}`
478
+ ) : null
479
+ ]);
480
+ };
481
+ }
482
+ });
483
+ var Segmented = (0, import_vue.defineComponent)({
484
+ name: "VspSegmented",
485
+ props: {
486
+ modelValue: { type: String, default: void 0 },
487
+ options: { type: Array, default: () => [] }
488
+ },
489
+ emits: ["update:modelValue"],
490
+ setup(props, { emit }) {
491
+ return () => (0, import_vue.h)(
492
+ "div",
493
+ { class: "ui-seg" },
494
+ props.options.map(
495
+ (o) => (0, import_vue.h)(
496
+ "button",
497
+ {
498
+ key: o,
499
+ type: "button",
500
+ class: cx(props.modelValue === o && "on"),
501
+ onClick: () => emit("update:modelValue", o)
502
+ },
503
+ o
504
+ )
505
+ )
506
+ );
507
+ }
508
+ });
509
+ var ICON_PATHS = { chevL: "M15 18l-6-6 6-6", chevR: "M9 18l6-6-6-6", check: "M20 6L9 17l-5-5" };
510
+ var svgIcon = (d, size = 14) => (0, import_vue.h)(
511
+ "svg",
512
+ {
513
+ viewBox: "0 0 24 24",
514
+ width: size,
515
+ height: size,
516
+ fill: "none",
517
+ stroke: "currentColor",
518
+ "stroke-width": 2,
519
+ "stroke-linecap": "round",
520
+ "stroke-linejoin": "round"
521
+ },
522
+ [(0, import_vue.h)("path", { d })]
523
+ );
524
+ var Tabs = (0, import_vue.defineComponent)({
525
+ name: "VspTabs",
526
+ props: {
527
+ tabs: { type: Array, default: () => [] },
528
+ modelValue: { type: String, default: void 0 }
529
+ },
530
+ emits: ["update:modelValue"],
531
+ setup(props, { emit, slots }) {
532
+ return () => (0, import_vue.h)("div", { class: "ui-tabs", style: { alignItems: "center" } }, [
533
+ ...props.tabs.map((t) => {
534
+ const id = typeof t === "string" ? t : t.value;
535
+ const label = typeof t === "string" ? t : t.label;
536
+ const count = typeof t === "object" ? t.count : void 0;
537
+ return (0, import_vue.h)(
538
+ "button",
539
+ {
540
+ key: id,
541
+ type: "button",
542
+ class: cx("ui-tab", props.modelValue === id && "on"),
543
+ onClick: () => emit("update:modelValue", id)
544
+ },
545
+ [
546
+ label,
547
+ count != null ? (0, import_vue.h)("span", { class: "badge badge-muted", style: { marginLeft: "7px" } }, count) : null
548
+ ]
549
+ );
550
+ }),
551
+ slots.right ? (0, import_vue.h)("div", { style: { flex: 1 } }) : null,
552
+ slots.right?.()
553
+ ]);
554
+ }
555
+ });
556
+ var Breadcrumb = (0, import_vue.defineComponent)({
557
+ name: "VspBreadcrumb",
558
+ props: { items: { type: Array, default: () => [] } },
559
+ setup(props) {
560
+ return () => (0, import_vue.h)(
561
+ "nav",
562
+ { style: { display: "flex", alignItems: "center", gap: "7px", fontSize: "12.5px" } },
563
+ props.items.flatMap((it, i) => {
564
+ const last = i === props.items.length - 1;
565
+ return [
566
+ i > 0 ? (0, import_vue.h)(
567
+ "span",
568
+ { key: `s${i}`, style: { color: "var(--text-faint)", display: "flex" } },
569
+ [svgIcon(ICON_PATHS.chevR, 13)]
570
+ ) : null,
571
+ (0, import_vue.h)(
572
+ "span",
573
+ {
574
+ key: i,
575
+ style: {
576
+ color: last ? "var(--text)" : "var(--text-dim)",
577
+ fontWeight: last ? 600 : 500
578
+ }
579
+ },
580
+ it
581
+ )
582
+ ];
583
+ })
584
+ );
585
+ }
586
+ });
587
+ var Pagination = (0, import_vue.defineComponent)({
588
+ name: "VspPagination",
589
+ props: {
590
+ modelValue: { type: Number, default: 0 },
591
+ pages: { type: Number, default: 1 }
592
+ },
593
+ emits: ["update:modelValue"],
594
+ setup(props, { emit }) {
595
+ return () => {
596
+ const page = props.modelValue;
597
+ const nums = [];
598
+ for (let i = 0; i < props.pages; i++) {
599
+ if (i === 0 || i === props.pages - 1 || Math.abs(i - page) <= 1) nums.push(i);
600
+ else if (nums[nums.length - 1] !== "\u2026") nums.push("\u2026");
601
+ }
602
+ return (0, import_vue.h)("div", { style: { display: "flex", gap: "4px", alignItems: "center" } }, [
603
+ (0, import_vue.h)(
604
+ "button",
605
+ {
606
+ type: "button",
607
+ class: "btn btn-ghost btn-sm",
608
+ disabled: page === 0,
609
+ "aria-label": "Previous page",
610
+ onClick: () => emit("update:modelValue", page - 1)
611
+ },
612
+ [svgIcon(ICON_PATHS.chevL)]
613
+ ),
614
+ ...nums.map(
615
+ (n, i) => n === "\u2026" ? (0, import_vue.h)(
616
+ "span",
617
+ {
618
+ key: `g${i}`,
619
+ class: "mono",
620
+ style: { padding: "0 6px", color: "var(--text-faint)" }
621
+ },
622
+ "\u2026"
623
+ ) : (0, import_vue.h)(
624
+ "button",
625
+ {
626
+ key: n,
627
+ type: "button",
628
+ class: cx("btn", "btn-sm", n === page ? "btn-primary" : "btn-subtle"),
629
+ style: { minWidth: "32px", padding: 0 },
630
+ onClick: () => emit("update:modelValue", n)
631
+ },
632
+ n + 1
633
+ )
634
+ ),
635
+ (0, import_vue.h)(
636
+ "button",
637
+ {
638
+ type: "button",
639
+ class: "btn btn-ghost btn-sm",
640
+ disabled: page >= props.pages - 1,
641
+ "aria-label": "Next page",
642
+ onClick: () => emit("update:modelValue", page + 1)
643
+ },
644
+ [svgIcon(ICON_PATHS.chevR)]
645
+ )
646
+ ]);
647
+ };
648
+ }
649
+ });
650
+ var Stepper = (0, import_vue.defineComponent)({
651
+ name: "VspStepper",
652
+ props: {
653
+ steps: { type: Array, default: () => [] },
654
+ current: { type: Number, default: 0 }
655
+ },
656
+ setup(props) {
657
+ return () => (0, import_vue.h)(
658
+ "div",
659
+ { class: "ui-steps" },
660
+ props.steps.flatMap((s, i) => [
661
+ i > 0 ? (0, import_vue.h)("div", { key: `b${i}`, class: cx("ui-step-bar", i <= props.current && "done") }) : null,
662
+ (0, import_vue.h)(
663
+ "div",
664
+ {
665
+ key: i,
666
+ class: cx(
667
+ "ui-step",
668
+ i < props.current && "done",
669
+ i === props.current && "active",
670
+ i > props.current && "pending"
671
+ )
672
+ },
673
+ [
674
+ (0, import_vue.h)("span", { class: "ui-step-dot" }, [
675
+ i < props.current ? svgIcon(ICON_PATHS.check) : i + 1
676
+ ]),
677
+ (0, import_vue.h)("span", { class: "ui-step-label" }, s)
678
+ ]
679
+ )
680
+ ])
681
+ );
682
+ }
683
+ });
684
+ var CircularProgress = (0, import_vue.defineComponent)({
685
+ name: "VspCircularProgress",
686
+ props: {
687
+ value: { type: Number, default: 0 },
688
+ size: { type: Number, default: 76 },
689
+ thickness: { type: Number, default: 7 },
690
+ color: { type: String, default: "var(--accent)" },
691
+ label: { type: String, default: void 0 }
692
+ },
693
+ setup(props) {
694
+ return () => {
695
+ const r = (props.size - props.thickness) / 2;
696
+ const circ = 2 * Math.PI * r;
697
+ return (0, import_vue.h)(
698
+ "div",
699
+ { style: { position: "relative", width: px(props.size), height: px(props.size) } },
700
+ [
701
+ (0, import_vue.h)(
702
+ "svg",
703
+ { width: props.size, height: props.size, style: { transform: "rotate(-90deg)" } },
704
+ [
705
+ (0, import_vue.h)("circle", {
706
+ cx: props.size / 2,
707
+ cy: props.size / 2,
708
+ r,
709
+ fill: "none",
710
+ stroke: "var(--surface-3)",
711
+ "stroke-width": props.thickness
712
+ }),
713
+ (0, import_vue.h)("circle", {
714
+ cx: props.size / 2,
715
+ cy: props.size / 2,
716
+ r,
717
+ fill: "none",
718
+ stroke: props.color,
719
+ "stroke-width": props.thickness,
720
+ "stroke-linecap": "round",
721
+ "stroke-dasharray": circ,
722
+ "stroke-dashoffset": circ * (1 - Math.min(100, props.value) / 100),
723
+ style: { transition: "stroke-dashoffset .5s cubic-bezier(.3,.7,.3,1)" }
724
+ })
725
+ ]
726
+ ),
727
+ (0, import_vue.h)(
728
+ "div",
729
+ {
730
+ class: "tnum",
731
+ style: {
732
+ position: "absolute",
733
+ inset: 0,
734
+ display: "grid",
735
+ placeItems: "center",
736
+ fontWeight: 800,
737
+ fontSize: px(props.size * 0.24)
738
+ }
739
+ },
740
+ props.label ?? `${Math.round(props.value)}%`
741
+ )
742
+ ]
743
+ );
744
+ };
745
+ }
746
+ });
747
+ var Stat = (0, import_vue.defineComponent)({
748
+ name: "VspStat",
749
+ props: {
750
+ label: { type: String, default: void 0 },
751
+ value: { type: String, default: void 0 },
752
+ delta: { type: String, default: void 0 },
753
+ deltaDir: { type: String, default: "up" },
754
+ tone: { type: String, default: "var(--accent)" }
755
+ },
756
+ setup(props, { slots }) {
757
+ return () => (0, import_vue.h)(
758
+ "div",
759
+ { class: "card card-pad", style: { display: "flex", alignItems: "center", gap: "13px" } },
760
+ [
761
+ slots.icon ? (0, import_vue.h)(
762
+ "span",
763
+ {
764
+ style: {
765
+ width: "38px",
766
+ height: "38px",
767
+ borderRadius: "var(--r-sm)",
768
+ flexShrink: 0,
769
+ display: "grid",
770
+ placeItems: "center",
771
+ background: `color-mix(in oklab, ${props.tone} 14%, transparent)`,
772
+ color: props.tone
773
+ }
774
+ },
775
+ slots.icon()
776
+ ) : null,
777
+ (0, import_vue.h)("div", { style: { minWidth: 0 } }, [
778
+ (0, import_vue.h)("div", { class: "eyebrow" }, props.label),
779
+ (0, import_vue.h)(
780
+ "div",
781
+ { style: { display: "flex", alignItems: "baseline", gap: "8px", marginTop: "3px" } },
782
+ [
783
+ (0, import_vue.h)(
784
+ "span",
785
+ {
786
+ class: "tnum",
787
+ style: { fontSize: "22px", fontWeight: 800, letterSpacing: "-.02em" }
788
+ },
789
+ props.value
790
+ ),
791
+ props.delta != null ? (0, import_vue.h)(
792
+ "span",
793
+ {
794
+ class: cx("badge", props.deltaDir === "up" ? "badge-pos" : "badge-neg"),
795
+ style: { padding: "1px 6px" }
796
+ },
797
+ [
798
+ svgIcon(
799
+ props.deltaDir === "up" ? "M12 19V5M5 12l7-7 7 7" : "M12 5v14M5 12l7 7 7-7",
800
+ 10
801
+ ),
802
+ props.delta
803
+ ]
804
+ ) : null
805
+ ]
806
+ )
807
+ ])
808
+ ]
809
+ );
810
+ }
811
+ });
812
+ var clockSvg = (size = 14) => (0, import_vue.h)(
813
+ "svg",
814
+ {
815
+ viewBox: "0 0 24 24",
816
+ width: size,
817
+ height: size,
818
+ fill: "none",
819
+ stroke: "currentColor",
820
+ "stroke-width": 2,
821
+ "stroke-linecap": "round",
822
+ "stroke-linejoin": "round"
823
+ },
824
+ [(0, import_vue.h)("circle", { cx: 12, cy: 12, r: 9 }), (0, import_vue.h)("path", { d: "M12 7v5l3 2" })]
825
+ );
826
+ var TL_TONE = {
827
+ pos: "var(--success)",
828
+ neg: "var(--danger)",
829
+ warn: "var(--warning)",
830
+ info: "var(--accent)"
831
+ };
832
+ var Timeline = (0, import_vue.defineComponent)({
833
+ name: "VspTimeline",
834
+ props: { items: { type: Array, default: () => [] } },
835
+ setup(props) {
836
+ return () => (0, import_vue.h)(
837
+ "div",
838
+ { class: "ui-tl" },
839
+ props.items.map((it, i) => {
840
+ const c = it.tone ? TL_TONE[it.tone] : void 0;
841
+ return (0, import_vue.h)("div", { key: i, class: "ui-tl-item" }, [
842
+ (0, import_vue.h)(
843
+ "span",
844
+ {
845
+ class: "ui-tl-dot",
846
+ style: c ? {
847
+ background: `color-mix(in oklab, ${c} 14%, transparent)`,
848
+ color: c,
849
+ borderColor: `color-mix(in oklab, ${c} 30%, transparent)`
850
+ } : void 0
851
+ },
852
+ [clockSvg()]
853
+ ),
854
+ (0, import_vue.h)("div", { class: "ui-tl-body" }, [
855
+ (0, import_vue.h)(
856
+ "div",
857
+ {
858
+ style: { display: "flex", alignItems: "baseline", gap: "8px", flexWrap: "wrap" }
859
+ },
860
+ [
861
+ (0, import_vue.h)("span", { style: { fontWeight: 600, fontSize: "13.5px" } }, it.title),
862
+ it.time ? (0, import_vue.h)("span", { class: "eyebrow", style: { marginLeft: "auto" } }, it.time) : null
863
+ ]
864
+ ),
865
+ it.body ? (0, import_vue.h)(
866
+ "div",
867
+ { style: { fontSize: "12.5px", color: "var(--text-dim)", marginTop: "3px" } },
868
+ it.body
869
+ ) : null
870
+ ])
871
+ ]);
872
+ })
873
+ );
874
+ }
875
+ });
876
+ var DescriptionList = (0, import_vue.defineComponent)({
877
+ name: "VspDescriptionList",
878
+ props: { items: { type: Array, default: () => [] } },
879
+ setup(props) {
880
+ return () => (0, import_vue.h)(
881
+ "dl",
882
+ { class: "ui-dl" },
883
+ props.items.flatMap(([k, v], i) => {
884
+ const last = i === props.items.length - 1 ? "last" : "";
885
+ return [
886
+ (0, import_vue.h)("dt", { key: `k${i}`, class: last }, k),
887
+ (0, import_vue.h)("dd", { key: `v${i}`, class: last }, v)
888
+ ];
889
+ })
890
+ );
891
+ }
892
+ });
893
+ var BANNER_ICON = {
894
+ info: "M12 3l1.6 5L19 9.6l-5 1.6L12 16l-1.6-4.8L5 9.6l5.4-1.6z",
895
+ warn: "M18 8a6 6 0 00-12 0c0 7-3 9-3 9h18s-3-2-3-9",
896
+ accent: "M13 2L3 14h9l-1 8 10-12h-9l1-8z"
897
+ };
898
+ var X_PATH = "M18 6L6 18M6 6l12 12";
899
+ var INBOX_PATH = "M22 12h-6l-2 3h-4l-2-3H2M5.45 5.11L2 12v6a2 2 0 002 2h16a2 2 0 002-2v-6l-3.45-6.89A2 2 0 0016.76 4H7.24a2 2 0 00-1.79 1.11z";
900
+ var Banner = (0, import_vue.defineComponent)({
901
+ name: "VspBanner",
902
+ props: {
903
+ tone: { type: String, default: "info" },
904
+ dismissible: Boolean
905
+ },
906
+ emits: ["dismiss"],
907
+ setup(props, { slots, emit }) {
908
+ return () => (0, import_vue.h)("div", { class: cx("ui-banner", props.tone) }, [
909
+ slots.icon ? slots.icon() : svgIcon(BANNER_ICON[props.tone], 18),
910
+ (0, import_vue.h)("div", { style: { flex: 1, fontSize: "13px", fontWeight: 500 } }, slots.default?.()),
911
+ slots.action?.(),
912
+ props.dismissible ? (0, import_vue.h)(
913
+ "button",
914
+ {
915
+ type: "button",
916
+ class: "ui-banner-x",
917
+ "aria-label": "Dismiss",
918
+ onClick: () => emit("dismiss")
919
+ },
920
+ [svgIcon(X_PATH, 15)]
921
+ ) : null
922
+ ]);
923
+ }
924
+ });
925
+ var EmptyState = (0, import_vue.defineComponent)({
926
+ name: "VspEmptyState",
927
+ props: {
928
+ title: { type: String, default: void 0 },
929
+ desc: { type: String, default: void 0 },
930
+ compact: Boolean
931
+ },
932
+ setup(props, { slots }) {
933
+ return () => (0, import_vue.h)(
934
+ "div",
935
+ {
936
+ style: {
937
+ display: "grid",
938
+ placeItems: "center",
939
+ textAlign: "center",
940
+ padding: props.compact ? "32px 20px" : "56px 24px"
941
+ }
942
+ },
943
+ [
944
+ (0, import_vue.h)("div", { style: { maxWidth: "340px" } }, [
945
+ (0, import_vue.h)(
946
+ "span",
947
+ {
948
+ style: {
949
+ width: "56px",
950
+ height: "56px",
951
+ borderRadius: "16px",
952
+ display: "grid",
953
+ placeItems: "center",
954
+ margin: "0 auto 18px",
955
+ background: "color-mix(in oklab, var(--accent) 12%, transparent)",
956
+ color: "var(--accent)",
957
+ border: "1px solid color-mix(in oklab, var(--accent) 22%, transparent)"
958
+ }
959
+ },
960
+ slots.icon ? slots.icon() : [svgIcon(INBOX_PATH, 26)]
961
+ ),
962
+ (0, import_vue.h)("div", { style: { fontSize: "17px", fontWeight: 700 } }, props.title),
963
+ props.desc ? (0, import_vue.h)(
964
+ "p",
965
+ {
966
+ style: {
967
+ margin: "7px 0 0",
968
+ color: "var(--text-dim)",
969
+ fontSize: "13.5px",
970
+ lineHeight: 1.6
971
+ }
972
+ },
973
+ props.desc
974
+ ) : null,
975
+ slots.action ? (0, import_vue.h)(
976
+ "div",
977
+ {
978
+ style: {
979
+ marginTop: "20px",
980
+ display: "flex",
981
+ gap: "8px",
982
+ justifyContent: "center"
983
+ }
984
+ },
985
+ slots.action()
986
+ ) : null
987
+ ])
988
+ ]
989
+ );
990
+ }
991
+ });
992
+ var Accordion = (0, import_vue.defineComponent)({
993
+ name: "VspAccordion",
994
+ props: {
995
+ items: { type: Array, default: () => [] },
996
+ multiple: Boolean,
997
+ defaultOpen: { type: Array, default: () => [] }
998
+ },
999
+ setup(props) {
1000
+ const open = (0, import_vue.ref)(new Set(props.defaultOpen));
1001
+ const toggle = (i) => {
1002
+ const s = open.value;
1003
+ const n = new Set(props.multiple ? s : []);
1004
+ if (s.has(i)) n.delete(i);
1005
+ else n.add(i);
1006
+ open.value = n;
1007
+ };
1008
+ return () => (0, import_vue.h)(
1009
+ "div",
1010
+ { class: "ui-acc" },
1011
+ props.items.map(
1012
+ (it, i) => (0, import_vue.h)("div", { key: i, class: cx("ui-acc-item", open.value.has(i) && "open") }, [
1013
+ (0, import_vue.h)("button", { type: "button", class: "ui-acc-head", onClick: () => toggle(i) }, [
1014
+ it.title,
1015
+ (0, import_vue.h)(
1016
+ "svg",
1017
+ {
1018
+ class: "chev",
1019
+ viewBox: "0 0 24 24",
1020
+ width: 17,
1021
+ height: 17,
1022
+ fill: "none",
1023
+ stroke: "currentColor",
1024
+ "stroke-width": 2,
1025
+ "stroke-linecap": "round",
1026
+ "stroke-linejoin": "round"
1027
+ },
1028
+ [(0, import_vue.h)("path", { d: ICON_PATHS.chevR })]
1029
+ )
1030
+ ]),
1031
+ (0, import_vue.h)("div", { class: "ui-acc-bodywrap" }, [
1032
+ (0, import_vue.h)("div", null, [(0, import_vue.h)("div", { class: "ui-acc-body" }, it.body)])
1033
+ ])
1034
+ ])
1035
+ )
1036
+ );
1037
+ }
1038
+ });
1039
+ function smoothPath(pts) {
1040
+ if (pts.length < 2) return "";
1041
+ let d = `M ${pts[0][0]} ${pts[0][1]}`;
1042
+ for (let i = 0; i < pts.length - 1; i++) {
1043
+ const [x0, y0] = pts[i];
1044
+ const [x1, y1] = pts[i + 1];
1045
+ const cx2 = (x0 + x1) / 2;
1046
+ d += ` C ${cx2} ${y0} ${cx2} ${y1} ${x1} ${y1}`;
1047
+ }
1048
+ return d;
1049
+ }
1050
+ var Sparkline = (0, import_vue.defineComponent)({
1051
+ name: "VspSparkline",
1052
+ props: {
1053
+ data: { type: Array, default: () => [] },
1054
+ color: { type: String, default: "var(--accent)" },
1055
+ w: { type: Number, default: 110 },
1056
+ h: { type: Number, default: 34 },
1057
+ fill: { type: Boolean, default: true }
1058
+ },
1059
+ setup(props) {
1060
+ const gid = "spk" + (0, import_vue.useId)().replace(/[^a-zA-Z0-9]/g, "");
1061
+ return () => {
1062
+ const data = props.data;
1063
+ const min = Math.min(...data);
1064
+ const max = Math.max(...data);
1065
+ const rng = max - min || 1;
1066
+ const pts = data.map((v, i) => [
1067
+ i / (data.length - 1) * props.w,
1068
+ props.h - 3 - (v - min) / rng * (props.h - 6)
1069
+ ]);
1070
+ const d = smoothPath(pts);
1071
+ const last = pts[pts.length - 1] ?? [0, 0];
1072
+ return (0, import_vue.h)(
1073
+ "svg",
1074
+ {
1075
+ width: props.w,
1076
+ height: props.h,
1077
+ viewBox: `0 0 ${props.w} ${props.h}`,
1078
+ style: { display: "block", overflow: "visible" }
1079
+ },
1080
+ [
1081
+ props.fill ? (0, import_vue.h)("defs", null, [
1082
+ (0, import_vue.h)("linearGradient", { id: gid, x1: "0", x2: "0", y1: "0", y2: "1" }, [
1083
+ (0, import_vue.h)("stop", { offset: "0", "stop-color": props.color, "stop-opacity": "0.28" }),
1084
+ (0, import_vue.h)("stop", { offset: "1", "stop-color": props.color, "stop-opacity": "0" })
1085
+ ])
1086
+ ]) : null,
1087
+ props.fill ? (0, import_vue.h)("path", {
1088
+ d: `${d} L ${props.w} ${props.h} L 0 ${props.h} Z`,
1089
+ fill: `url(#${gid})`
1090
+ }) : null,
1091
+ (0, import_vue.h)("path", {
1092
+ d,
1093
+ fill: "none",
1094
+ stroke: props.color,
1095
+ "stroke-width": "2",
1096
+ "stroke-linecap": "round"
1097
+ }),
1098
+ (0, import_vue.h)("circle", { cx: last[0], cy: last[1], r: "2.6", fill: props.color })
1099
+ ]
1100
+ );
1101
+ };
1102
+ }
1103
+ });
1104
+ var Donut = (0, import_vue.defineComponent)({
1105
+ name: "VspDonut",
1106
+ props: {
1107
+ data: { type: Array, default: () => [] },
1108
+ size: { type: Number, default: 168 },
1109
+ thickness: { type: Number, default: 22 }
1110
+ },
1111
+ setup(props) {
1112
+ return () => {
1113
+ const total = props.data.reduce((s, d) => s + d.value, 0) || 1;
1114
+ const r = (props.size - props.thickness) / 2;
1115
+ const c = props.size / 2;
1116
+ const circ = 2 * Math.PI * r;
1117
+ let acc = 0;
1118
+ const segs = props.data.map((d, i) => {
1119
+ const len = d.value / total * circ;
1120
+ const seg = (0, import_vue.h)("circle", {
1121
+ key: i,
1122
+ cx: c,
1123
+ cy: c,
1124
+ r,
1125
+ fill: "none",
1126
+ stroke: d.color,
1127
+ "stroke-width": props.thickness,
1128
+ "stroke-dasharray": `${len - 2.5} ${circ - len + 2.5}`,
1129
+ "stroke-dashoffset": -acc,
1130
+ "stroke-linecap": "round"
1131
+ });
1132
+ acc += len;
1133
+ return seg;
1134
+ });
1135
+ return (0, import_vue.h)("div", { style: { display: "flex", alignItems: "center", gap: "22px" } }, [
1136
+ (0, import_vue.h)(
1137
+ "svg",
1138
+ {
1139
+ width: props.size,
1140
+ height: props.size,
1141
+ style: { transform: "rotate(-90deg)", flexShrink: 0 }
1142
+ },
1143
+ [
1144
+ (0, import_vue.h)("circle", {
1145
+ cx: c,
1146
+ cy: c,
1147
+ r,
1148
+ fill: "none",
1149
+ stroke: "var(--surface-3)",
1150
+ "stroke-width": props.thickness
1151
+ }),
1152
+ ...segs
1153
+ ]
1154
+ ),
1155
+ (0, import_vue.h)(
1156
+ "div",
1157
+ { style: { display: "flex", flexDirection: "column", gap: "9px", flex: 1 } },
1158
+ props.data.map(
1159
+ (d, i) => (0, import_vue.h)(
1160
+ "div",
1161
+ {
1162
+ key: i,
1163
+ style: { display: "flex", alignItems: "center", gap: "9px", fontSize: "12.5px" }
1164
+ },
1165
+ [
1166
+ (0, import_vue.h)("i", {
1167
+ style: {
1168
+ width: "9px",
1169
+ height: "9px",
1170
+ borderRadius: "3px",
1171
+ background: d.color,
1172
+ flexShrink: 0
1173
+ }
1174
+ }),
1175
+ (0, import_vue.h)("span", { style: { color: "var(--text-dim)", flex: 1 } }, d.label),
1176
+ (0, import_vue.h)(
1177
+ "span",
1178
+ { class: "mono tnum", style: { fontWeight: 600 } },
1179
+ `${Math.round(d.value / total * 100)}%`
1180
+ )
1181
+ ]
1182
+ )
1183
+ )
1184
+ )
1185
+ ]);
1186
+ };
1187
+ }
1188
+ });
1189
+ var StatCard = (0, import_vue.defineComponent)({
1190
+ name: "VspStatCard",
1191
+ props: {
1192
+ label: { type: String, default: void 0 },
1193
+ value: { type: String, default: void 0 },
1194
+ delta: { type: String, default: void 0 },
1195
+ deltaDir: { type: String, default: "up" },
1196
+ spark: { type: Array, default: void 0 },
1197
+ sparkColor: { type: String, default: "var(--accent)" }
1198
+ },
1199
+ setup(props, { slots }) {
1200
+ return () => (0, import_vue.h)(
1201
+ "div",
1202
+ {
1203
+ class: "card card-pad vsp-rise",
1204
+ style: { display: "flex", flexDirection: "column", gap: "14px" }
1205
+ },
1206
+ [
1207
+ (0, import_vue.h)(
1208
+ "div",
1209
+ { style: { display: "flex", alignItems: "center", justifyContent: "space-between" } },
1210
+ [
1211
+ (0, import_vue.h)("div", { style: { display: "flex", alignItems: "center", gap: "10px" } }, [
1212
+ (0, import_vue.h)(
1213
+ "span",
1214
+ {
1215
+ style: {
1216
+ width: "34px",
1217
+ height: "34px",
1218
+ borderRadius: "var(--r-sm)",
1219
+ display: "grid",
1220
+ placeItems: "center",
1221
+ background: "color-mix(in oklab, var(--accent) 13%, transparent)",
1222
+ color: "var(--accent)"
1223
+ }
1224
+ },
1225
+ slots.icon?.()
1226
+ ),
1227
+ (0, import_vue.h)("span", { class: "eyebrow" }, props.label)
1228
+ ]),
1229
+ props.delta != null ? (0, import_vue.h)(
1230
+ "span",
1231
+ { class: cx("badge", props.deltaDir === "up" ? "badge-pos" : "badge-neg") },
1232
+ [
1233
+ svgIcon(
1234
+ props.deltaDir === "up" ? "M12 19V5M5 12l7-7 7 7" : "M12 5v14M5 12l7 7 7-7",
1235
+ 11
1236
+ ),
1237
+ props.delta
1238
+ ]
1239
+ ) : null
1240
+ ]
1241
+ ),
1242
+ (0, import_vue.h)(
1243
+ "div",
1244
+ {
1245
+ style: {
1246
+ display: "flex",
1247
+ alignItems: "flex-end",
1248
+ justifyContent: "space-between",
1249
+ gap: "12px"
1250
+ }
1251
+ },
1252
+ [
1253
+ (0, import_vue.h)(
1254
+ "div",
1255
+ {
1256
+ class: "tnum",
1257
+ style: {
1258
+ fontSize: "30px",
1259
+ fontWeight: 800,
1260
+ letterSpacing: "-.02em",
1261
+ lineHeight: 1
1262
+ }
1263
+ },
1264
+ props.value
1265
+ ),
1266
+ props.spark ? (0, import_vue.h)(Sparkline, { data: props.spark, color: props.sparkColor }) : null
1267
+ ]
1268
+ )
1269
+ ]
1270
+ );
1271
+ }
1272
+ });
254
1273
  // Annotate the CommonJS export names for ESM import in node:
255
1274
  0 && (module.exports = {
1275
+ Accordion,
256
1276
  Alert,
1277
+ Avatar,
1278
+ AvatarGroup,
257
1279
  Badge,
1280
+ Banner,
1281
+ Breadcrumb,
258
1282
  Button,
259
1283
  Card,
260
1284
  CardHead,
261
1285
  Checkbox,
1286
+ CircularProgress,
1287
+ DescriptionList,
262
1288
  Divider,
1289
+ Donut,
1290
+ EmptyState,
263
1291
  Field,
264
1292
  IconButton,
265
1293
  Input,
266
1294
  Kbd,
1295
+ NativeSelect,
1296
+ Pagination,
1297
+ Progress,
1298
+ Radio,
1299
+ RadioGroup,
1300
+ Segmented,
1301
+ Skeleton,
1302
+ Slider,
1303
+ Sparkline,
267
1304
  Spinner,
1305
+ Stat,
1306
+ StatCard,
1307
+ Stepper,
268
1308
  Switch,
1309
+ Tabs,
269
1310
  Tag,
270
- Textarea
1311
+ Textarea,
1312
+ Timeline,
1313
+ smoothPath
271
1314
  });
272
1315
  //# sourceMappingURL=index.cjs.map