@unicitylabs/sphere-ui 0.1.9 → 0.1.12

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/README.md CHANGED
@@ -7,7 +7,7 @@ Shared UI library for the Sphere ecosystem. Provides a unified design system, co
7
7
  | App | Description |
8
8
  |-----|-------------|
9
9
  | [sphere](https://github.com/unicity-sphere/sphere) | Wallet & marketplace |
10
- | [sphere-dev](https://github.com/unicity-sphere/sphere-dev) | Developer Portal |
10
+ | [sphere-dev-portal](https://github.com/unicity-sphere/sphere-dev-portal) | Developer Portal |
11
11
  | [sphere-backoffice](https://github.com/unicity-sphere/sphere-backoffice) | Admin panel |
12
12
  | [sphere-quest](https://github.com/unicity-sphere/sphere-quest) | Quest frontend (iframe) |
13
13
 
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ interface DashboardLayoutProps {
15
15
  }
16
16
  /**
17
17
  * Dashboard shell with sidebar + main content area.
18
- * Used by sphere-backoffice and sphere-dev.
18
+ * Used by sphere-backoffice and sphere-dev-portal.
19
19
  */
20
20
  declare function DashboardLayout({ logo, nav, footer, children }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
21
21
 
@@ -137,6 +137,29 @@ interface EmptyStateProps {
137
137
  }
138
138
  declare function EmptyState({ title, description, action }: EmptyStateProps): react_jsx_runtime.JSX.Element;
139
139
 
140
+ interface SkeletonProps {
141
+ width?: string;
142
+ height?: string;
143
+ radius?: string;
144
+ className?: string;
145
+ }
146
+ declare function Skeleton({ width, height, radius, className, }: SkeletonProps): react_jsx_runtime.JSX.Element;
147
+
148
+ interface SkeletonTextProps {
149
+ lines?: number;
150
+ lineHeight?: string;
151
+ gap?: string;
152
+ className?: string;
153
+ }
154
+ declare function SkeletonText({ lines, lineHeight, gap, className, }: SkeletonTextProps): react_jsx_runtime.JSX.Element;
155
+
156
+ type SkeletonCircleSize = 'sm' | 'md' | 'lg' | (string & {});
157
+ interface SkeletonCircleProps {
158
+ size?: SkeletonCircleSize;
159
+ className?: string;
160
+ }
161
+ declare function SkeletonCircle({ size, className }: SkeletonCircleProps): react_jsx_runtime.JSX.Element;
162
+
140
163
  interface SelectOption {
141
164
  value: string;
142
165
  label: string;
@@ -255,4 +278,4 @@ declare const IconStar: (p: IconProps) => react_jsx_runtime.JSX.Element;
255
278
  declare const IconDiamond: (p: IconProps) => react_jsx_runtime.JSX.Element;
256
279
  declare const IconCircle: (p: IconProps) => react_jsx_runtime.JSX.Element;
257
280
 
258
- export { AddressDisplay, AlertBanner, AppLogo, Button, type ButtonProps, type ButtonVariant, ChainInput, ConfirmDialog, CustomSelect, DashboardLayout, DataTable, EmptyState, Field, FormModal, IconArrowRight, IconBack, IconChain, IconCheck, IconChevronDown, IconChevronUp, IconChevronsDown, IconChevronsRight, IconCircle, IconDiamond, IconEdit, IconPlay, IconPlus, IconQuests, IconSearch, IconSettings, IconStar, IconTracks, IconTrash, IconUndo, IconX, Input, type InputProps, JsonPanel, JsonToggleButton, type MemoCondition, MemoConditionsEditor, type NavGroup, type NavItem, PageShell, SearchInput, Section, Select, type SelectOption, type SelectProps, SidebarNav, StatusBadge, Textarea, type TextareaProps, tagColor };
281
+ export { AddressDisplay, AlertBanner, AppLogo, Button, type ButtonProps, type ButtonVariant, ChainInput, ConfirmDialog, CustomSelect, DashboardLayout, DataTable, EmptyState, Field, FormModal, IconArrowRight, IconBack, IconChain, IconCheck, IconChevronDown, IconChevronUp, IconChevronsDown, IconChevronsRight, IconCircle, IconDiamond, IconEdit, IconPlay, IconPlus, IconQuests, IconSearch, IconSettings, IconStar, IconTracks, IconTrash, IconUndo, IconX, Input, type InputProps, JsonPanel, JsonToggleButton, type MemoCondition, MemoConditionsEditor, type NavGroup, type NavItem, PageShell, SearchInput, Section, Select, type SelectOption, type SelectProps, SidebarNav, Skeleton, SkeletonCircle, type SkeletonCircleProps, type SkeletonCircleSize, type SkeletonProps, SkeletonText, type SkeletonTextProps, StatusBadge, Textarea, type TextareaProps, tagColor };
package/dist/index.js CHANGED
@@ -479,10 +479,81 @@ function EmptyState({ title, description, action }) {
479
479
  ] });
480
480
  }
481
481
 
482
+ // src/components/Skeleton.tsx
483
+ import { jsx as jsx11 } from "react/jsx-runtime";
484
+ function Skeleton({
485
+ width = "100%",
486
+ height = "1rem",
487
+ radius = "var(--radius-md)",
488
+ className = ""
489
+ }) {
490
+ return /* @__PURE__ */ jsx11(
491
+ "div",
492
+ {
493
+ className: `animate-skeleton-pulse ${className}`.trim(),
494
+ "aria-busy": "true",
495
+ style: {
496
+ width,
497
+ height,
498
+ borderRadius: radius,
499
+ background: "var(--bg-hover)",
500
+ border: "1px solid var(--border)"
501
+ }
502
+ }
503
+ );
504
+ }
505
+
506
+ // src/components/SkeletonText.tsx
507
+ import { jsx as jsx12 } from "react/jsx-runtime";
508
+ function SkeletonText({
509
+ lines = 1,
510
+ lineHeight = "0.875rem",
511
+ gap = "0.5rem",
512
+ className = ""
513
+ }) {
514
+ return /* @__PURE__ */ jsx12(
515
+ "div",
516
+ {
517
+ className,
518
+ role: "status",
519
+ "aria-busy": "true",
520
+ style: { display: "flex", flexDirection: "column", gap },
521
+ children: Array.from({ length: lines }).map((_, i) => {
522
+ const isLast = i === lines - 1 && lines > 1;
523
+ return /* @__PURE__ */ jsx12(
524
+ Skeleton,
525
+ {
526
+ width: isLast ? "70%" : "100%",
527
+ height: lineHeight,
528
+ radius: "var(--radius-sm)"
529
+ },
530
+ i
531
+ );
532
+ })
533
+ }
534
+ );
535
+ }
536
+
537
+ // src/components/SkeletonCircle.tsx
538
+ import { jsx as jsx13 } from "react/jsx-runtime";
539
+ var NAMED_SIZES = {
540
+ sm: "1.5rem",
541
+ md: "2.5rem",
542
+ lg: "4rem"
543
+ };
544
+ function resolveSize(size) {
545
+ if (size === "sm" || size === "md" || size === "lg") return NAMED_SIZES[size];
546
+ return size;
547
+ }
548
+ function SkeletonCircle({ size = "md", className = "" }) {
549
+ const dim = resolveSize(size);
550
+ return /* @__PURE__ */ jsx13(Skeleton, { width: dim, height: dim, radius: "50%", className });
551
+ }
552
+
482
553
  // src/components/CustomSelect.tsx
483
554
  import { useState as useState2, useRef, useEffect as useEffect3, useCallback } from "react";
484
555
  import { createPortal as createPortal3 } from "react-dom";
485
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
556
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
486
557
  function CustomSelect({
487
558
  options,
488
559
  value,
@@ -545,8 +616,8 @@ function CustomSelect({
545
616
  className: `admin-input w-full flex items-center justify-between gap-2 ${textSize} text-left`,
546
617
  style: { color: selected ? "var(--text-primary)" : "var(--text-muted)" },
547
618
  children: [
548
- /* @__PURE__ */ jsx11("span", { className: "truncate", children: label }),
549
- /* @__PURE__ */ jsx11(
619
+ /* @__PURE__ */ jsx14("span", { className: "truncate", children: label }),
620
+ /* @__PURE__ */ jsx14(
550
621
  "svg",
551
622
  {
552
623
  width: "12",
@@ -562,14 +633,14 @@ function CustomSelect({
562
633
  color: "var(--text-muted)",
563
634
  transform: open ? "rotate(180deg)" : "rotate(0deg)"
564
635
  },
565
- children: /* @__PURE__ */ jsx11("path", { d: "M3 4.5L6 7.5L9 4.5" })
636
+ children: /* @__PURE__ */ jsx14("path", { d: "M3 4.5L6 7.5L9 4.5" })
566
637
  }
567
638
  )
568
639
  ]
569
640
  }
570
641
  ),
571
642
  open && createPortal3(
572
- /* @__PURE__ */ jsx11(
643
+ /* @__PURE__ */ jsx14(
573
644
  "div",
574
645
  {
575
646
  ref: dropRef,
@@ -586,7 +657,7 @@ function CustomSelect({
586
657
  borderRadius: "var(--radius-md)",
587
658
  boxShadow: "0 8px 24px rgba(0,0,0,0.4)"
588
659
  },
589
- children: options.map((opt) => /* @__PURE__ */ jsx11(
660
+ children: options.map((opt) => /* @__PURE__ */ jsx14(
590
661
  "button",
591
662
  {
592
663
  type: "button",
@@ -617,17 +688,17 @@ function CustomSelect({
617
688
  }
618
689
 
619
690
  // src/components/PageShell.tsx
620
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
691
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
621
692
  function PageShell({ title, subtitle, action, maxWidth, children }) {
622
693
  return /* @__PURE__ */ jsxs11("div", { className: `p-6 lg:p-8 ${maxWidth ?? ""}`, children: [
623
694
  /* @__PURE__ */ jsxs11("div", { className: "page-header", children: [
624
695
  /* @__PURE__ */ jsxs11("div", { children: [
625
- /* @__PURE__ */ jsx12("h1", { className: "page-title", children: title }),
626
- subtitle && /* @__PURE__ */ jsx12("p", { className: "page-subtitle", children: subtitle })
696
+ /* @__PURE__ */ jsx15("h1", { className: "page-title", children: title }),
697
+ subtitle && /* @__PURE__ */ jsx15("p", { className: "page-subtitle", children: subtitle })
627
698
  ] }),
628
699
  action
629
700
  ] }),
630
- /* @__PURE__ */ jsx12("div", { className: "animate-fade-in", children })
701
+ /* @__PURE__ */ jsx15("div", { className: "animate-fade-in", children })
631
702
  ] });
632
703
  }
633
704
 
@@ -640,7 +711,7 @@ import {
640
711
  getFilteredRowModel,
641
712
  flexRender
642
713
  } from "@tanstack/react-table";
643
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
714
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
644
715
  function DataTable({
645
716
  columns,
646
717
  data,
@@ -664,10 +735,10 @@ function DataTable({
664
735
  getFilteredRowModel: getFilteredRowModel()
665
736
  });
666
737
  if (isLoading) {
667
- return /* @__PURE__ */ jsx13("div", { className: "py-12 text-center", style: { color: "var(--text-muted)" }, children: "Loading\u2026" });
738
+ return /* @__PURE__ */ jsx16("div", { className: "py-12 text-center", style: { color: "var(--text-muted)" }, children: "Loading\u2026" });
668
739
  }
669
740
  return /* @__PURE__ */ jsxs12("div", { children: [
670
- enableSearch && /* @__PURE__ */ jsx13("div", { className: "mb-4 max-w-xs", children: /* @__PURE__ */ jsx13(
741
+ enableSearch && /* @__PURE__ */ jsx16("div", { className: "mb-4 max-w-xs", children: /* @__PURE__ */ jsx16(
671
742
  SearchInput,
672
743
  {
673
744
  value: globalFilter,
@@ -675,21 +746,21 @@ function DataTable({
675
746
  placeholder: searchPlaceholder ?? "Search..."
676
747
  }
677
748
  ) }),
678
- table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx13(EmptyState, { title: emptyMessage }) : /* @__PURE__ */ jsx13("div", { className: "admin-card overflow-hidden", children: /* @__PURE__ */ jsxs12("table", { className: "admin-table", children: [
679
- /* @__PURE__ */ jsx13("thead", { children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsx13("tr", { children: hg.headers.map((header) => /* @__PURE__ */ jsx13(
749
+ table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx16(EmptyState, { title: emptyMessage }) : /* @__PURE__ */ jsx16("div", { className: "admin-card overflow-hidden", children: /* @__PURE__ */ jsxs12("table", { className: "admin-table", children: [
750
+ /* @__PURE__ */ jsx16("thead", { children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsx16("tr", { children: hg.headers.map((header) => /* @__PURE__ */ jsx16(
680
751
  "th",
681
752
  {
682
753
  className: header.column.getCanSort() ? "cursor-pointer select-none" : "",
683
754
  onClick: header.column.getToggleSortingHandler(),
684
755
  children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
685
756
  flexRender(header.column.columnDef.header, header.getContext()),
686
- header.column.getIsSorted() === "asc" && /* @__PURE__ */ jsx13("span", { style: { color: "var(--accent-text)" }, children: "\u25B2" }),
687
- header.column.getIsSorted() === "desc" && /* @__PURE__ */ jsx13("span", { style: { color: "var(--accent-text)" }, children: "\u25BC" })
757
+ header.column.getIsSorted() === "asc" && /* @__PURE__ */ jsx16("span", { style: { color: "var(--accent-text)" }, children: "\u25B2" }),
758
+ header.column.getIsSorted() === "desc" && /* @__PURE__ */ jsx16("span", { style: { color: "var(--accent-text)" }, children: "\u25BC" })
688
759
  ] })
689
760
  },
690
761
  header.id
691
762
  )) }, hg.id)) }),
692
- /* @__PURE__ */ jsx13("tbody", { children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx13(
763
+ /* @__PURE__ */ jsx16("tbody", { children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx16(
693
764
  "tr",
694
765
  {
695
766
  onClick: () => onRowClick?.(row.original),
@@ -701,7 +772,7 @@ function DataTable({
701
772
  onMouseLeave: (e) => {
702
773
  if (onRowClick) e.currentTarget.style.background = "";
703
774
  },
704
- children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx13("td", { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
775
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx16("td", { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
705
776
  },
706
777
  row.id
707
778
  )) })
@@ -710,7 +781,7 @@ function DataTable({
710
781
  }
711
782
 
712
783
  // src/components/AlertBanner.tsx
713
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
784
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
714
785
  var STYLES = {
715
786
  warning: {
716
787
  bg: "rgba(251,191,36,0.06)",
@@ -727,16 +798,16 @@ var STYLES = {
727
798
  };
728
799
  function AlertBanner({ type, title, children }) {
729
800
  const s = STYLES[type];
730
- return /* @__PURE__ */ jsx14(
801
+ return /* @__PURE__ */ jsx17(
731
802
  "div",
732
803
  {
733
804
  className: "rounded-lg p-3 text-sm",
734
805
  style: { background: s.bg, border: `1px solid ${s.border}` },
735
806
  children: /* @__PURE__ */ jsxs13("div", { className: "flex items-start gap-2", children: [
736
- /* @__PURE__ */ jsx14("span", { className: "flex-shrink-0 text-sm leading-5", children: s.icon }),
807
+ /* @__PURE__ */ jsx17("span", { className: "flex-shrink-0 text-sm leading-5", children: s.icon }),
737
808
  /* @__PURE__ */ jsxs13("div", { children: [
738
- /* @__PURE__ */ jsx14("div", { className: "font-medium text-xs mb-0.5", style: { color: s.titleColor }, children: title }),
739
- /* @__PURE__ */ jsx14("div", { style: { color: "var(--text-muted)", fontSize: "0.75rem", lineHeight: "1.4" }, children })
809
+ /* @__PURE__ */ jsx17("div", { className: "font-medium text-xs mb-0.5", style: { color: s.titleColor }, children: title }),
810
+ /* @__PURE__ */ jsx17("div", { style: { color: "var(--text-muted)", fontSize: "0.75rem", lineHeight: "1.4" }, children })
740
811
  ] })
741
812
  ] })
742
813
  }
@@ -745,7 +816,7 @@ function AlertBanner({ type, title, children }) {
745
816
 
746
817
  // src/components/AddressDisplay.tsx
747
818
  import { useState as useState4, useCallback as useCallback2 } from "react";
748
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
819
+ import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
749
820
  function truncateAddress(address) {
750
821
  const prefix = "DIRECT://";
751
822
  if (address.startsWith(prefix)) {
@@ -769,12 +840,12 @@ function AddressDisplay({ address, nametag, truncate = true }) {
769
840
  }, [address]);
770
841
  const displayAddress = truncate ? truncateAddress(address) : address;
771
842
  return /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5 min-w-0", children: [
772
- /* @__PURE__ */ jsx15("div", { className: "min-w-0 flex-1", children: nametag ? /* @__PURE__ */ jsxs14(Fragment2, { children: [
843
+ /* @__PURE__ */ jsx18("div", { className: "min-w-0 flex-1", children: nametag ? /* @__PURE__ */ jsxs14(Fragment2, { children: [
773
844
  /* @__PURE__ */ jsxs14("div", { className: "text-sm font-medium", style: { color: "var(--text-primary)" }, children: [
774
845
  "@",
775
846
  nametag
776
847
  ] }),
777
- /* @__PURE__ */ jsx15(
848
+ /* @__PURE__ */ jsx18(
778
849
  "div",
779
850
  {
780
851
  className: "text-[11px] font-mono truncate",
@@ -783,7 +854,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
783
854
  children: displayAddress
784
855
  }
785
856
  )
786
- ] }) : /* @__PURE__ */ jsx15(
857
+ ] }) : /* @__PURE__ */ jsx18(
787
858
  "div",
788
859
  {
789
860
  className: "text-xs font-mono truncate",
@@ -792,7 +863,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
792
863
  children: displayAddress
793
864
  }
794
865
  ) }),
795
- /* @__PURE__ */ jsx15(
866
+ /* @__PURE__ */ jsx18(
796
867
  "button",
797
868
  {
798
869
  onClick: handleCopy,
@@ -805,9 +876,9 @@ function AddressDisplay({ address, nametag, truncate = true }) {
805
876
  if (!copied) e.currentTarget.style.color = "var(--text-muted)";
806
877
  },
807
878
  title: copied ? "Copied!" : "Copy address",
808
- children: copied ? /* @__PURE__ */ jsx15("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx15("path", { d: "M20 6L9 17l-5-5" }) }) : /* @__PURE__ */ jsxs14("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
809
- /* @__PURE__ */ jsx15("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
810
- /* @__PURE__ */ jsx15("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
879
+ children: copied ? /* @__PURE__ */ jsx18("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx18("path", { d: "M20 6L9 17l-5-5" }) }) : /* @__PURE__ */ jsxs14("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
880
+ /* @__PURE__ */ jsx18("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
881
+ /* @__PURE__ */ jsx18("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
811
882
  ] })
812
883
  }
813
884
  )
@@ -816,7 +887,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
816
887
 
817
888
  // src/components/JsonPanel.tsx
818
889
  import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback3 } from "react";
819
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
890
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
820
891
  function JsonPanel({
821
892
  value,
822
893
  onChange,
@@ -864,9 +935,9 @@ function JsonPanel({
864
935
  " ",
865
936
  title
866
937
  ] }),
867
- parseError && /* @__PURE__ */ jsx16("span", { className: "text-[10px] px-1.5 py-0.5 rounded", style: { background: "rgba(239,68,68,0.1)", color: "#f87171" }, children: "Error" })
938
+ parseError && /* @__PURE__ */ jsx19("span", { className: "text-[10px] px-1.5 py-0.5 rounded", style: { background: "rgba(239,68,68,0.1)", color: "#f87171" }, children: "Error" })
868
939
  ] }),
869
- /* @__PURE__ */ jsx16(
940
+ /* @__PURE__ */ jsx19(
870
941
  "button",
871
942
  {
872
943
  onClick: handleCopy,
@@ -887,8 +958,8 @@ function JsonPanel({
887
958
  }
888
959
  )
889
960
  ] }),
890
- /* @__PURE__ */ jsx16("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxs15("div", { className: "absolute inset-0 flex overflow-auto", children: [
891
- /* @__PURE__ */ jsx16(
961
+ /* @__PURE__ */ jsx19("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxs15("div", { className: "absolute inset-0 flex overflow-auto", children: [
962
+ /* @__PURE__ */ jsx19(
892
963
  "div",
893
964
  {
894
965
  className: "shrink-0 text-right pr-2 pt-3 select-none",
@@ -902,10 +973,10 @@ function JsonPanel({
902
973
  background: "var(--bg-surface)",
903
974
  borderRight: "1px solid var(--border)"
904
975
  },
905
- children: Array.from({ length: lineCount }, (_, i) => /* @__PURE__ */ jsx16("div", { children: i + 1 }, i))
976
+ children: Array.from({ length: lineCount }, (_, i) => /* @__PURE__ */ jsx19("div", { children: i + 1 }, i))
906
977
  }
907
978
  ),
908
- /* @__PURE__ */ jsx16(
979
+ /* @__PURE__ */ jsx19(
909
980
  "textarea",
910
981
  {
911
982
  ref: textareaRef,
@@ -927,11 +998,11 @@ function JsonPanel({
927
998
  }
928
999
  )
929
1000
  ] }) }),
930
- parseError && /* @__PURE__ */ jsx16("div", { className: "px-3 py-1.5 text-[10px]", style: { background: "rgba(239,68,68,0.06)", color: "#f87171", borderTop: "1px solid rgba(239,68,68,0.15)" }, children: parseError })
1001
+ parseError && /* @__PURE__ */ jsx19("div", { className: "px-3 py-1.5 text-[10px]", style: { background: "rgba(239,68,68,0.06)", color: "#f87171", borderTop: "1px solid rgba(239,68,68,0.15)" }, children: parseError })
931
1002
  ] });
932
1003
  }
933
1004
  function JsonToggleButton({ active, onClick }) {
934
- return /* @__PURE__ */ jsx16(
1005
+ return /* @__PURE__ */ jsx19(
935
1006
  "button",
936
1007
  {
937
1008
  onClick,
@@ -963,7 +1034,7 @@ function JsonToggleButton({ active, onClick }) {
963
1034
 
964
1035
  // src/components/ChainInput.tsx
965
1036
  import { useState as useState6, useId } from "react";
966
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1037
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
967
1038
  var TAG_PALETTES = [
968
1039
  { bg: "rgba(16, 185, 129, 0.12)", color: "#34d399", border: "rgba(16, 185, 129, 0.2)" },
969
1040
  { bg: "rgba(59, 130, 246, 0.12)", color: "#60a5fa", border: "rgba(59, 130, 246, 0.2)" },
@@ -1033,7 +1104,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1033
1104
  style: { background: c.bg, color: c.color, border: `1px solid ${c.border}` },
1034
1105
  children: [
1035
1106
  name,
1036
- /* @__PURE__ */ jsx17(
1107
+ /* @__PURE__ */ jsx20(
1037
1108
  "input",
1038
1109
  {
1039
1110
  type: "number",
@@ -1045,7 +1116,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1045
1116
  onClick: (e) => e.stopPropagation()
1046
1117
  }
1047
1118
  ),
1048
- /* @__PURE__ */ jsx17(
1119
+ /* @__PURE__ */ jsx20(
1049
1120
  "button",
1050
1121
  {
1051
1122
  type: "button",
@@ -1062,7 +1133,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1062
1133
  name
1063
1134
  );
1064
1135
  }),
1065
- /* @__PURE__ */ jsx17(
1136
+ /* @__PURE__ */ jsx20(
1066
1137
  "input",
1067
1138
  {
1068
1139
  id: inputId,
@@ -1122,7 +1193,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1122
1193
  e.currentTarget.style.background = "transparent";
1123
1194
  },
1124
1195
  children: [
1125
- /* @__PURE__ */ jsx17("span", { className: "w-2 h-2 rounded-full flex-shrink-0", style: { background: c.color } }),
1196
+ /* @__PURE__ */ jsx20("span", { className: "w-2 h-2 rounded-full flex-shrink-0", style: { background: c.color } }),
1126
1197
  s
1127
1198
  ]
1128
1199
  },
@@ -1136,7 +1207,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1136
1207
  }
1137
1208
 
1138
1209
  // src/components/MemoConditionsEditor.tsx
1139
- import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1210
+ import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
1140
1211
  var MEMO_OPERATORS = [
1141
1212
  { value: "eq", label: "Equals (=)", values: 1 },
1142
1213
  { value: "neq", label: "Not equals (\u2260)", values: 1 },
@@ -1184,14 +1255,14 @@ function MemoConditionsEditor({ conditions, onChange }) {
1184
1255
  const opMeta = (op) => MEMO_OPERATORS.find((o) => o.value === op) ?? MEMO_OPERATORS[0];
1185
1256
  return /* @__PURE__ */ jsxs17("div", { children: [
1186
1257
  /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 mb-2", children: [
1187
- /* @__PURE__ */ jsx18("span", { className: "text-xs font-medium", style: { color: "var(--text-muted)" }, children: "Memo Conditions" }),
1188
- /* @__PURE__ */ jsx18("button", { type: "button", onClick: add, className: "text-[10px] px-2 py-0.5 rounded cursor-pointer", style: { background: "var(--bg-elevated)", color: "var(--accent-text)" }, children: "+ Add" })
1258
+ /* @__PURE__ */ jsx21("span", { className: "text-xs font-medium", style: { color: "var(--text-muted)" }, children: "Memo Conditions" }),
1259
+ /* @__PURE__ */ jsx21("button", { type: "button", onClick: add, className: "text-[10px] px-2 py-0.5 rounded cursor-pointer", style: { background: "var(--bg-elevated)", color: "var(--accent-text)" }, children: "+ Add" })
1189
1260
  ] }),
1190
- conditions.length > 0 ? /* @__PURE__ */ jsx18("div", { className: "flex flex-col gap-2", children: conditions.map((cond, i) => {
1261
+ conditions.length > 0 ? /* @__PURE__ */ jsx21("div", { className: "flex flex-col gap-2", children: conditions.map((cond, i) => {
1191
1262
  const meta = opMeta(cond.operator);
1192
1263
  return /* @__PURE__ */ jsxs17("div", { className: "rounded-lg p-2.5", style: { background: "rgba(255,255,255,0.02)", border: "1px solid var(--border)" }, children: [
1193
1264
  /* @__PURE__ */ jsxs17("div", { className: "flex gap-2 items-center", children: [
1194
- /* @__PURE__ */ jsx18(
1265
+ /* @__PURE__ */ jsx21(
1195
1266
  "input",
1196
1267
  {
1197
1268
  className: "admin-input flex-1 text-xs",
@@ -1200,7 +1271,7 @@ function MemoConditionsEditor({ conditions, onChange }) {
1200
1271
  onChange: (e) => update(i, { key: e.target.value })
1201
1272
  }
1202
1273
  ),
1203
- /* @__PURE__ */ jsx18(
1274
+ /* @__PURE__ */ jsx21(
1204
1275
  CustomSelect,
1205
1276
  {
1206
1277
  size: "sm",
@@ -1210,7 +1281,7 @@ function MemoConditionsEditor({ conditions, onChange }) {
1210
1281
  options: MEMO_OPERATORS.map((o) => ({ value: o.value, label: o.label }))
1211
1282
  }
1212
1283
  ),
1213
- meta.values >= 1 && /* @__PURE__ */ jsx18(
1284
+ meta.values >= 1 && /* @__PURE__ */ jsx21(
1214
1285
  "input",
1215
1286
  {
1216
1287
  className: "admin-input flex-1 text-xs",
@@ -1220,8 +1291,8 @@ function MemoConditionsEditor({ conditions, onChange }) {
1220
1291
  }
1221
1292
  ),
1222
1293
  meta.values === 2 && /* @__PURE__ */ jsxs17(Fragment3, { children: [
1223
- /* @__PURE__ */ jsx18("span", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: "and" }),
1224
- /* @__PURE__ */ jsx18(
1294
+ /* @__PURE__ */ jsx21("span", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: "and" }),
1295
+ /* @__PURE__ */ jsx21(
1225
1296
  "input",
1226
1297
  {
1227
1298
  className: "admin-input flex-1 text-xs",
@@ -1231,18 +1302,18 @@ function MemoConditionsEditor({ conditions, onChange }) {
1231
1302
  }
1232
1303
  )
1233
1304
  ] }),
1234
- /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => remove(i), className: "text-red-400 text-sm px-1 cursor-pointer", children: "\xD7" })
1305
+ /* @__PURE__ */ jsx21("button", { type: "button", onClick: () => remove(i), className: "text-red-400 text-sm px-1 cursor-pointer", children: "\xD7" })
1235
1306
  ] }),
1236
- /* @__PURE__ */ jsx18("p", { className: "text-[10px] mt-1.5 ml-0.5", style: { color: "var(--text-muted)", fontStyle: "italic" }, children: conditionPreview(cond) })
1307
+ /* @__PURE__ */ jsx21("p", { className: "text-[10px] mt-1.5 ml-0.5", style: { color: "var(--text-muted)", fontStyle: "italic" }, children: conditionPreview(cond) })
1237
1308
  ] }, i);
1238
- }) }) : /* @__PURE__ */ jsx18("p", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: 'No conditions \u2014 matches any memo transaction. Click "+ Add" to filter.' })
1309
+ }) }) : /* @__PURE__ */ jsx21("p", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: 'No conditions \u2014 matches any memo transaction. Click "+ Add" to filter.' })
1239
1310
  ] });
1240
1311
  }
1241
1312
 
1242
1313
  // src/components/Icons.tsx
1243
- import { jsx as jsx19 } from "react/jsx-runtime";
1314
+ import { jsx as jsx22 } from "react/jsx-runtime";
1244
1315
  function I({ d, size = 16, className, style }) {
1245
- return /* @__PURE__ */ jsx19(
1316
+ return /* @__PURE__ */ jsx22(
1246
1317
  "svg",
1247
1318
  {
1248
1319
  width: size,
@@ -1255,7 +1326,7 @@ function I({ d, size = 16, className, style }) {
1255
1326
  strokeLinejoin: "round",
1256
1327
  className,
1257
1328
  style,
1258
- children: /* @__PURE__ */ jsx19("path", { d })
1329
+ children: /* @__PURE__ */ jsx22("path", { d })
1259
1330
  }
1260
1331
  );
1261
1332
  }
@@ -1287,27 +1358,27 @@ var P = {
1287
1358
  diamond: "M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0L2.7 10.3z",
1288
1359
  circle: "M12 12m-4 0a4 4 0 1 0 8 0 4 4 0 1 0-8 0"
1289
1360
  };
1290
- var IconBack = (p) => /* @__PURE__ */ jsx19(I, { d: P.back, ...p });
1291
- var IconUndo = (p) => /* @__PURE__ */ jsx19(I, { d: P.undo, ...p });
1292
- var IconQuests = (p) => /* @__PURE__ */ jsx19(I, { d: P.quests, ...p });
1293
- var IconTracks = (p) => /* @__PURE__ */ jsx19(I, { d: P.tracks, ...p });
1294
- var IconSettings = (p) => /* @__PURE__ */ jsx19(I, { d: P.settings, ...p });
1295
- var IconChain = (p) => /* @__PURE__ */ jsx19(I, { d: P.chain, ...p });
1296
- var IconPlus = (p) => /* @__PURE__ */ jsx19(I, { d: P.plus, ...p });
1297
- var IconEdit = (p) => /* @__PURE__ */ jsx19(I, { d: P.edit, ...p });
1298
- var IconTrash = (p) => /* @__PURE__ */ jsx19(I, { d: P.trash, ...p });
1299
- var IconX = (p) => /* @__PURE__ */ jsx19(I, { d: P.x, ...p });
1300
- var IconCheck = (p) => /* @__PURE__ */ jsx19(I, { d: P.check, ...p });
1301
- var IconSearch = (p) => /* @__PURE__ */ jsx19(I, { d: P.search, ...p });
1302
- var IconChevronUp = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronUp, ...p });
1303
- var IconChevronDown = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronDown, ...p });
1304
- var IconChevronsDown = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronsDown, ...p });
1305
- var IconChevronsRight = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronsRight, ...p });
1306
- var IconArrowRight = (p) => /* @__PURE__ */ jsx19(I, { d: P.arrowRight, ...p });
1307
- var IconPlay = (p) => /* @__PURE__ */ jsx19(I, { d: P.play, ...p });
1308
- var IconStar = (p) => /* @__PURE__ */ jsx19(I, { d: P.star, ...p });
1309
- var IconDiamond = (p) => /* @__PURE__ */ jsx19(I, { d: P.diamond, ...p });
1310
- var IconCircle = (p) => /* @__PURE__ */ jsx19(I, { d: P.circle, ...p });
1361
+ var IconBack = (p) => /* @__PURE__ */ jsx22(I, { d: P.back, ...p });
1362
+ var IconUndo = (p) => /* @__PURE__ */ jsx22(I, { d: P.undo, ...p });
1363
+ var IconQuests = (p) => /* @__PURE__ */ jsx22(I, { d: P.quests, ...p });
1364
+ var IconTracks = (p) => /* @__PURE__ */ jsx22(I, { d: P.tracks, ...p });
1365
+ var IconSettings = (p) => /* @__PURE__ */ jsx22(I, { d: P.settings, ...p });
1366
+ var IconChain = (p) => /* @__PURE__ */ jsx22(I, { d: P.chain, ...p });
1367
+ var IconPlus = (p) => /* @__PURE__ */ jsx22(I, { d: P.plus, ...p });
1368
+ var IconEdit = (p) => /* @__PURE__ */ jsx22(I, { d: P.edit, ...p });
1369
+ var IconTrash = (p) => /* @__PURE__ */ jsx22(I, { d: P.trash, ...p });
1370
+ var IconX = (p) => /* @__PURE__ */ jsx22(I, { d: P.x, ...p });
1371
+ var IconCheck = (p) => /* @__PURE__ */ jsx22(I, { d: P.check, ...p });
1372
+ var IconSearch = (p) => /* @__PURE__ */ jsx22(I, { d: P.search, ...p });
1373
+ var IconChevronUp = (p) => /* @__PURE__ */ jsx22(I, { d: P.chevronUp, ...p });
1374
+ var IconChevronDown = (p) => /* @__PURE__ */ jsx22(I, { d: P.chevronDown, ...p });
1375
+ var IconChevronsDown = (p) => /* @__PURE__ */ jsx22(I, { d: P.chevronsDown, ...p });
1376
+ var IconChevronsRight = (p) => /* @__PURE__ */ jsx22(I, { d: P.chevronsRight, ...p });
1377
+ var IconArrowRight = (p) => /* @__PURE__ */ jsx22(I, { d: P.arrowRight, ...p });
1378
+ var IconPlay = (p) => /* @__PURE__ */ jsx22(I, { d: P.play, ...p });
1379
+ var IconStar = (p) => /* @__PURE__ */ jsx22(I, { d: P.star, ...p });
1380
+ var IconDiamond = (p) => /* @__PURE__ */ jsx22(I, { d: P.diamond, ...p });
1381
+ var IconCircle = (p) => /* @__PURE__ */ jsx22(I, { d: P.circle, ...p });
1311
1382
  export {
1312
1383
  AddressDisplay,
1313
1384
  AlertBanner,
@@ -1351,6 +1422,9 @@ export {
1351
1422
  Section,
1352
1423
  Select,
1353
1424
  SidebarNav,
1425
+ Skeleton,
1426
+ SkeletonCircle,
1427
+ SkeletonText,
1354
1428
  StatusBadge,
1355
1429
  Textarea,
1356
1430
  tagColor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/sphere-ui",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -35,29 +35,37 @@
35
35
  "build": "tsup",
36
36
  "dev": "tsup --watch",
37
37
  "lint": "eslint src/",
38
- "typecheck": "tsc --noEmit"
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest",
40
+ "test:run": "vitest run"
39
41
  },
40
42
  "peerDependencies": {
41
- "react": "^19.0.0",
42
- "react-dom": "^19.0.0",
43
+ "@dnd-kit/core": "^6.0.0",
44
+ "@dnd-kit/sortable": "^8.0.0 || ^10.0.0",
43
45
  "@tanstack/react-query": "^5.0.0",
44
46
  "@tanstack/react-table": "^8.0.0",
45
- "@dnd-kit/core": "^6.0.0",
46
- "@dnd-kit/sortable": "^8.0.0",
47
- "lucide-react": "^0.400.0"
47
+ "lucide-react": "^0.400.0",
48
+ "react": "^19.0.0",
49
+ "react-dom": "^19.0.0"
48
50
  },
49
51
  "devDependencies": {
52
+ "@dnd-kit/core": "^6.0.0",
53
+ "@dnd-kit/sortable": "^10.0.0",
54
+ "@dnd-kit/utilities": "^3.0.0",
55
+ "@tanstack/react-query": "^5.40.0",
56
+ "@tanstack/react-table": "^8.17.3",
57
+ "@testing-library/dom": "^10.4.1",
58
+ "@testing-library/jest-dom": "^6.9.1",
59
+ "@testing-library/react": "^16.3.2",
50
60
  "@types/react": "^19.0.0",
51
61
  "@types/react-dom": "^19.0.0",
62
+ "@vitejs/plugin-react": "^4.7.0",
63
+ "jsdom": "^25.0.1",
64
+ "lucide-react": "^0.400.0",
52
65
  "react": "^19.0.0",
53
66
  "react-dom": "^19.0.0",
54
- "@tanstack/react-query": "^5.40.0",
55
- "@tanstack/react-table": "^8.17.3",
56
- "@dnd-kit/core": "^6.0.0",
57
- "@dnd-kit/sortable": "^8.0.0",
58
- "@dnd-kit/utilities": "^3.0.0",
59
- "lucide-react": "^0.400.0",
60
67
  "tsup": "^8.0.0",
61
- "typescript": "~5.9.0"
68
+ "typescript": "~5.9.0",
69
+ "vitest": "^2.1.9"
62
70
  }
63
71
  }
@@ -119,3 +119,12 @@ select.admin-input {
119
119
  @keyframes pulse-glow { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.8; } }
120
120
  @keyframes fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
121
121
  .animate-fade-in { animation: fade-in 0.3s ease-out both; }
122
+
123
+ /* ─── Skeleton ────────────────────────────────────────────────────────── */
124
+ @keyframes skeleton-pulse {
125
+ 0%, 100% { opacity: 0.6; }
126
+ 50% { opacity: 1; }
127
+ }
128
+ .animate-skeleton-pulse {
129
+ animation: skeleton-pulse 1.6s ease-in-out infinite;
130
+ }
@@ -1,7 +1,7 @@
1
1
  /* ═══════════════════════════════════════════════════════════════════════════
2
2
  Sphere UI — Design Tokens
3
3
  Brand: Unicity Brand Guidelines
4
- Used by: sphere, sphere-backoffice, sphere-dev, sphere-quest
4
+ Used by: sphere, sphere-backoffice, sphere-dev-portal, sphere-quest
5
5
  ═══════════════════════════════════════════════════════════════════════════ */
6
6
 
7
7
  /* Brand fonts — Anton (headlines), Geist (body), Geist Mono (code) */