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