@unicitylabs/sphere-ui 0.1.8 → 0.1.11

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
@@ -396,13 +396,19 @@ function ConfirmDialog({
396
396
  // src/components/StatusBadge.tsx
397
397
  import { jsx as jsx8 } from "react/jsx-runtime";
398
398
  var STATUS_COLORS = {
399
+ // Quest/Achievement statuses (uppercase)
399
400
  ACTIVE: "badge-green",
400
401
  DRAFT: "badge-gray",
401
402
  PAUSED: "badge-yellow",
402
403
  EXPIRED: "badge-red",
403
404
  ENDED: "badge-red",
404
405
  AWARDED: "badge-green",
405
- REJECTED: "badge-red"
406
+ REJECTED: "badge-red",
407
+ // Project statuses (lowercase)
408
+ draft: "badge-gray",
409
+ review: "badge-yellow",
410
+ published: "badge-green",
411
+ suspended: "badge-red"
406
412
  };
407
413
  function StatusBadge({ status, className = "" }) {
408
414
  return /* @__PURE__ */ jsx8("span", { className: `badge ${STATUS_COLORS[status] ?? "badge-gray"} ${className}`, children: status });
@@ -473,10 +479,81 @@ function EmptyState({ title, description, action }) {
473
479
  ] });
474
480
  }
475
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
+
476
553
  // src/components/CustomSelect.tsx
477
554
  import { useState as useState2, useRef, useEffect as useEffect3, useCallback } from "react";
478
555
  import { createPortal as createPortal3 } from "react-dom";
479
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
556
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
480
557
  function CustomSelect({
481
558
  options,
482
559
  value,
@@ -539,8 +616,8 @@ function CustomSelect({
539
616
  className: `admin-input w-full flex items-center justify-between gap-2 ${textSize} text-left`,
540
617
  style: { color: selected ? "var(--text-primary)" : "var(--text-muted)" },
541
618
  children: [
542
- /* @__PURE__ */ jsx11("span", { className: "truncate", children: label }),
543
- /* @__PURE__ */ jsx11(
619
+ /* @__PURE__ */ jsx14("span", { className: "truncate", children: label }),
620
+ /* @__PURE__ */ jsx14(
544
621
  "svg",
545
622
  {
546
623
  width: "12",
@@ -556,14 +633,14 @@ function CustomSelect({
556
633
  color: "var(--text-muted)",
557
634
  transform: open ? "rotate(180deg)" : "rotate(0deg)"
558
635
  },
559
- 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" })
560
637
  }
561
638
  )
562
639
  ]
563
640
  }
564
641
  ),
565
642
  open && createPortal3(
566
- /* @__PURE__ */ jsx11(
643
+ /* @__PURE__ */ jsx14(
567
644
  "div",
568
645
  {
569
646
  ref: dropRef,
@@ -580,7 +657,7 @@ function CustomSelect({
580
657
  borderRadius: "var(--radius-md)",
581
658
  boxShadow: "0 8px 24px rgba(0,0,0,0.4)"
582
659
  },
583
- children: options.map((opt) => /* @__PURE__ */ jsx11(
660
+ children: options.map((opt) => /* @__PURE__ */ jsx14(
584
661
  "button",
585
662
  {
586
663
  type: "button",
@@ -611,17 +688,17 @@ function CustomSelect({
611
688
  }
612
689
 
613
690
  // src/components/PageShell.tsx
614
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
691
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
615
692
  function PageShell({ title, subtitle, action, maxWidth, children }) {
616
693
  return /* @__PURE__ */ jsxs11("div", { className: `p-6 lg:p-8 ${maxWidth ?? ""}`, children: [
617
694
  /* @__PURE__ */ jsxs11("div", { className: "page-header", children: [
618
695
  /* @__PURE__ */ jsxs11("div", { children: [
619
- /* @__PURE__ */ jsx12("h1", { className: "page-title", children: title }),
620
- 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 })
621
698
  ] }),
622
699
  action
623
700
  ] }),
624
- /* @__PURE__ */ jsx12("div", { className: "animate-fade-in", children })
701
+ /* @__PURE__ */ jsx15("div", { className: "animate-fade-in", children })
625
702
  ] });
626
703
  }
627
704
 
@@ -634,7 +711,7 @@ import {
634
711
  getFilteredRowModel,
635
712
  flexRender
636
713
  } from "@tanstack/react-table";
637
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
714
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
638
715
  function DataTable({
639
716
  columns,
640
717
  data,
@@ -658,10 +735,10 @@ function DataTable({
658
735
  getFilteredRowModel: getFilteredRowModel()
659
736
  });
660
737
  if (isLoading) {
661
- 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" });
662
739
  }
663
740
  return /* @__PURE__ */ jsxs12("div", { children: [
664
- 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(
665
742
  SearchInput,
666
743
  {
667
744
  value: globalFilter,
@@ -669,21 +746,21 @@ function DataTable({
669
746
  placeholder: searchPlaceholder ?? "Search..."
670
747
  }
671
748
  ) }),
672
- 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: [
673
- /* @__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(
674
751
  "th",
675
752
  {
676
753
  className: header.column.getCanSort() ? "cursor-pointer select-none" : "",
677
754
  onClick: header.column.getToggleSortingHandler(),
678
755
  children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
679
756
  flexRender(header.column.columnDef.header, header.getContext()),
680
- header.column.getIsSorted() === "asc" && /* @__PURE__ */ jsx13("span", { style: { color: "var(--accent-text)" }, children: "\u25B2" }),
681
- 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" })
682
759
  ] })
683
760
  },
684
761
  header.id
685
762
  )) }, hg.id)) }),
686
- /* @__PURE__ */ jsx13("tbody", { children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx13(
763
+ /* @__PURE__ */ jsx16("tbody", { children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx16(
687
764
  "tr",
688
765
  {
689
766
  onClick: () => onRowClick?.(row.original),
@@ -695,7 +772,7 @@ function DataTable({
695
772
  onMouseLeave: (e) => {
696
773
  if (onRowClick) e.currentTarget.style.background = "";
697
774
  },
698
- 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))
699
776
  },
700
777
  row.id
701
778
  )) })
@@ -704,7 +781,7 @@ function DataTable({
704
781
  }
705
782
 
706
783
  // src/components/AlertBanner.tsx
707
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
784
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
708
785
  var STYLES = {
709
786
  warning: {
710
787
  bg: "rgba(251,191,36,0.06)",
@@ -721,16 +798,16 @@ var STYLES = {
721
798
  };
722
799
  function AlertBanner({ type, title, children }) {
723
800
  const s = STYLES[type];
724
- return /* @__PURE__ */ jsx14(
801
+ return /* @__PURE__ */ jsx17(
725
802
  "div",
726
803
  {
727
804
  className: "rounded-lg p-3 text-sm",
728
805
  style: { background: s.bg, border: `1px solid ${s.border}` },
729
806
  children: /* @__PURE__ */ jsxs13("div", { className: "flex items-start gap-2", children: [
730
- /* @__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 }),
731
808
  /* @__PURE__ */ jsxs13("div", { children: [
732
- /* @__PURE__ */ jsx14("div", { className: "font-medium text-xs mb-0.5", style: { color: s.titleColor }, children: title }),
733
- /* @__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 })
734
811
  ] })
735
812
  ] })
736
813
  }
@@ -739,7 +816,7 @@ function AlertBanner({ type, title, children }) {
739
816
 
740
817
  // src/components/AddressDisplay.tsx
741
818
  import { useState as useState4, useCallback as useCallback2 } from "react";
742
- 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";
743
820
  function truncateAddress(address) {
744
821
  const prefix = "DIRECT://";
745
822
  if (address.startsWith(prefix)) {
@@ -763,12 +840,12 @@ function AddressDisplay({ address, nametag, truncate = true }) {
763
840
  }, [address]);
764
841
  const displayAddress = truncate ? truncateAddress(address) : address;
765
842
  return /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5 min-w-0", children: [
766
- /* @__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: [
767
844
  /* @__PURE__ */ jsxs14("div", { className: "text-sm font-medium", style: { color: "var(--text-primary)" }, children: [
768
845
  "@",
769
846
  nametag
770
847
  ] }),
771
- /* @__PURE__ */ jsx15(
848
+ /* @__PURE__ */ jsx18(
772
849
  "div",
773
850
  {
774
851
  className: "text-[11px] font-mono truncate",
@@ -777,7 +854,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
777
854
  children: displayAddress
778
855
  }
779
856
  )
780
- ] }) : /* @__PURE__ */ jsx15(
857
+ ] }) : /* @__PURE__ */ jsx18(
781
858
  "div",
782
859
  {
783
860
  className: "text-xs font-mono truncate",
@@ -786,7 +863,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
786
863
  children: displayAddress
787
864
  }
788
865
  ) }),
789
- /* @__PURE__ */ jsx15(
866
+ /* @__PURE__ */ jsx18(
790
867
  "button",
791
868
  {
792
869
  onClick: handleCopy,
@@ -799,9 +876,9 @@ function AddressDisplay({ address, nametag, truncate = true }) {
799
876
  if (!copied) e.currentTarget.style.color = "var(--text-muted)";
800
877
  },
801
878
  title: copied ? "Copied!" : "Copy address",
802
- 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: [
803
- /* @__PURE__ */ jsx15("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
804
- /* @__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" })
805
882
  ] })
806
883
  }
807
884
  )
@@ -810,7 +887,7 @@ function AddressDisplay({ address, nametag, truncate = true }) {
810
887
 
811
888
  // src/components/JsonPanel.tsx
812
889
  import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback3 } from "react";
813
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
890
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
814
891
  function JsonPanel({
815
892
  value,
816
893
  onChange,
@@ -858,9 +935,9 @@ function JsonPanel({
858
935
  " ",
859
936
  title
860
937
  ] }),
861
- 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" })
862
939
  ] }),
863
- /* @__PURE__ */ jsx16(
940
+ /* @__PURE__ */ jsx19(
864
941
  "button",
865
942
  {
866
943
  onClick: handleCopy,
@@ -881,8 +958,8 @@ function JsonPanel({
881
958
  }
882
959
  )
883
960
  ] }),
884
- /* @__PURE__ */ jsx16("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxs15("div", { className: "absolute inset-0 flex overflow-auto", children: [
885
- /* @__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(
886
963
  "div",
887
964
  {
888
965
  className: "shrink-0 text-right pr-2 pt-3 select-none",
@@ -896,10 +973,10 @@ function JsonPanel({
896
973
  background: "var(--bg-surface)",
897
974
  borderRight: "1px solid var(--border)"
898
975
  },
899
- 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))
900
977
  }
901
978
  ),
902
- /* @__PURE__ */ jsx16(
979
+ /* @__PURE__ */ jsx19(
903
980
  "textarea",
904
981
  {
905
982
  ref: textareaRef,
@@ -921,11 +998,11 @@ function JsonPanel({
921
998
  }
922
999
  )
923
1000
  ] }) }),
924
- 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 })
925
1002
  ] });
926
1003
  }
927
1004
  function JsonToggleButton({ active, onClick }) {
928
- return /* @__PURE__ */ jsx16(
1005
+ return /* @__PURE__ */ jsx19(
929
1006
  "button",
930
1007
  {
931
1008
  onClick,
@@ -957,7 +1034,7 @@ function JsonToggleButton({ active, onClick }) {
957
1034
 
958
1035
  // src/components/ChainInput.tsx
959
1036
  import { useState as useState6, useId } from "react";
960
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1037
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
961
1038
  var TAG_PALETTES = [
962
1039
  { bg: "rgba(16, 185, 129, 0.12)", color: "#34d399", border: "rgba(16, 185, 129, 0.2)" },
963
1040
  { bg: "rgba(59, 130, 246, 0.12)", color: "#60a5fa", border: "rgba(59, 130, 246, 0.2)" },
@@ -1027,7 +1104,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1027
1104
  style: { background: c.bg, color: c.color, border: `1px solid ${c.border}` },
1028
1105
  children: [
1029
1106
  name,
1030
- /* @__PURE__ */ jsx17(
1107
+ /* @__PURE__ */ jsx20(
1031
1108
  "input",
1032
1109
  {
1033
1110
  type: "number",
@@ -1039,7 +1116,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1039
1116
  onClick: (e) => e.stopPropagation()
1040
1117
  }
1041
1118
  ),
1042
- /* @__PURE__ */ jsx17(
1119
+ /* @__PURE__ */ jsx20(
1043
1120
  "button",
1044
1121
  {
1045
1122
  type: "button",
@@ -1056,7 +1133,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1056
1133
  name
1057
1134
  );
1058
1135
  }),
1059
- /* @__PURE__ */ jsx17(
1136
+ /* @__PURE__ */ jsx20(
1060
1137
  "input",
1061
1138
  {
1062
1139
  id: inputId,
@@ -1116,7 +1193,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1116
1193
  e.currentTarget.style.background = "transparent";
1117
1194
  },
1118
1195
  children: [
1119
- /* @__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 } }),
1120
1197
  s
1121
1198
  ]
1122
1199
  },
@@ -1130,7 +1207,7 @@ function ChainInput({ chains, suggestions, onChange, size = "md" }) {
1130
1207
  }
1131
1208
 
1132
1209
  // src/components/MemoConditionsEditor.tsx
1133
- 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";
1134
1211
  var MEMO_OPERATORS = [
1135
1212
  { value: "eq", label: "Equals (=)", values: 1 },
1136
1213
  { value: "neq", label: "Not equals (\u2260)", values: 1 },
@@ -1178,14 +1255,14 @@ function MemoConditionsEditor({ conditions, onChange }) {
1178
1255
  const opMeta = (op) => MEMO_OPERATORS.find((o) => o.value === op) ?? MEMO_OPERATORS[0];
1179
1256
  return /* @__PURE__ */ jsxs17("div", { children: [
1180
1257
  /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 mb-2", children: [
1181
- /* @__PURE__ */ jsx18("span", { className: "text-xs font-medium", style: { color: "var(--text-muted)" }, children: "Memo Conditions" }),
1182
- /* @__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" })
1183
1260
  ] }),
1184
- 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) => {
1185
1262
  const meta = opMeta(cond.operator);
1186
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: [
1187
1264
  /* @__PURE__ */ jsxs17("div", { className: "flex gap-2 items-center", children: [
1188
- /* @__PURE__ */ jsx18(
1265
+ /* @__PURE__ */ jsx21(
1189
1266
  "input",
1190
1267
  {
1191
1268
  className: "admin-input flex-1 text-xs",
@@ -1194,7 +1271,7 @@ function MemoConditionsEditor({ conditions, onChange }) {
1194
1271
  onChange: (e) => update(i, { key: e.target.value })
1195
1272
  }
1196
1273
  ),
1197
- /* @__PURE__ */ jsx18(
1274
+ /* @__PURE__ */ jsx21(
1198
1275
  CustomSelect,
1199
1276
  {
1200
1277
  size: "sm",
@@ -1204,7 +1281,7 @@ function MemoConditionsEditor({ conditions, onChange }) {
1204
1281
  options: MEMO_OPERATORS.map((o) => ({ value: o.value, label: o.label }))
1205
1282
  }
1206
1283
  ),
1207
- meta.values >= 1 && /* @__PURE__ */ jsx18(
1284
+ meta.values >= 1 && /* @__PURE__ */ jsx21(
1208
1285
  "input",
1209
1286
  {
1210
1287
  className: "admin-input flex-1 text-xs",
@@ -1214,8 +1291,8 @@ function MemoConditionsEditor({ conditions, onChange }) {
1214
1291
  }
1215
1292
  ),
1216
1293
  meta.values === 2 && /* @__PURE__ */ jsxs17(Fragment3, { children: [
1217
- /* @__PURE__ */ jsx18("span", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: "and" }),
1218
- /* @__PURE__ */ jsx18(
1294
+ /* @__PURE__ */ jsx21("span", { className: "text-[10px]", style: { color: "var(--text-muted)" }, children: "and" }),
1295
+ /* @__PURE__ */ jsx21(
1219
1296
  "input",
1220
1297
  {
1221
1298
  className: "admin-input flex-1 text-xs",
@@ -1225,18 +1302,18 @@ function MemoConditionsEditor({ conditions, onChange }) {
1225
1302
  }
1226
1303
  )
1227
1304
  ] }),
1228
- /* @__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" })
1229
1306
  ] }),
1230
- /* @__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) })
1231
1308
  ] }, i);
1232
- }) }) : /* @__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.' })
1233
1310
  ] });
1234
1311
  }
1235
1312
 
1236
1313
  // src/components/Icons.tsx
1237
- import { jsx as jsx19 } from "react/jsx-runtime";
1314
+ import { jsx as jsx22 } from "react/jsx-runtime";
1238
1315
  function I({ d, size = 16, className, style }) {
1239
- return /* @__PURE__ */ jsx19(
1316
+ return /* @__PURE__ */ jsx22(
1240
1317
  "svg",
1241
1318
  {
1242
1319
  width: size,
@@ -1249,7 +1326,7 @@ function I({ d, size = 16, className, style }) {
1249
1326
  strokeLinejoin: "round",
1250
1327
  className,
1251
1328
  style,
1252
- children: /* @__PURE__ */ jsx19("path", { d })
1329
+ children: /* @__PURE__ */ jsx22("path", { d })
1253
1330
  }
1254
1331
  );
1255
1332
  }
@@ -1281,27 +1358,27 @@ var P = {
1281
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",
1282
1359
  circle: "M12 12m-4 0a4 4 0 1 0 8 0 4 4 0 1 0-8 0"
1283
1360
  };
1284
- var IconBack = (p) => /* @__PURE__ */ jsx19(I, { d: P.back, ...p });
1285
- var IconUndo = (p) => /* @__PURE__ */ jsx19(I, { d: P.undo, ...p });
1286
- var IconQuests = (p) => /* @__PURE__ */ jsx19(I, { d: P.quests, ...p });
1287
- var IconTracks = (p) => /* @__PURE__ */ jsx19(I, { d: P.tracks, ...p });
1288
- var IconSettings = (p) => /* @__PURE__ */ jsx19(I, { d: P.settings, ...p });
1289
- var IconChain = (p) => /* @__PURE__ */ jsx19(I, { d: P.chain, ...p });
1290
- var IconPlus = (p) => /* @__PURE__ */ jsx19(I, { d: P.plus, ...p });
1291
- var IconEdit = (p) => /* @__PURE__ */ jsx19(I, { d: P.edit, ...p });
1292
- var IconTrash = (p) => /* @__PURE__ */ jsx19(I, { d: P.trash, ...p });
1293
- var IconX = (p) => /* @__PURE__ */ jsx19(I, { d: P.x, ...p });
1294
- var IconCheck = (p) => /* @__PURE__ */ jsx19(I, { d: P.check, ...p });
1295
- var IconSearch = (p) => /* @__PURE__ */ jsx19(I, { d: P.search, ...p });
1296
- var IconChevronUp = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronUp, ...p });
1297
- var IconChevronDown = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronDown, ...p });
1298
- var IconChevronsDown = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronsDown, ...p });
1299
- var IconChevronsRight = (p) => /* @__PURE__ */ jsx19(I, { d: P.chevronsRight, ...p });
1300
- var IconArrowRight = (p) => /* @__PURE__ */ jsx19(I, { d: P.arrowRight, ...p });
1301
- var IconPlay = (p) => /* @__PURE__ */ jsx19(I, { d: P.play, ...p });
1302
- var IconStar = (p) => /* @__PURE__ */ jsx19(I, { d: P.star, ...p });
1303
- var IconDiamond = (p) => /* @__PURE__ */ jsx19(I, { d: P.diamond, ...p });
1304
- 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 });
1305
1382
  export {
1306
1383
  AddressDisplay,
1307
1384
  AlertBanner,
@@ -1345,6 +1422,9 @@ export {
1345
1422
  Section,
1346
1423
  Select,
1347
1424
  SidebarNav,
1425
+ Skeleton,
1426
+ SkeletonCircle,
1427
+ SkeletonText,
1348
1428
  StatusBadge,
1349
1429
  Textarea,
1350
1430
  tagColor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/sphere-ui",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
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
- "@tanstack/react-query": "^5.0.0",
44
- "@tanstack/react-table": "^8.0.0",
45
43
  "@dnd-kit/core": "^6.0.0",
46
44
  "@dnd-kit/sortable": "^8.0.0",
47
- "lucide-react": "^0.400.0"
45
+ "@tanstack/react-query": "^5.0.0",
46
+ "@tanstack/react-table": "^8.0.0",
47
+ "lucide-react": "^0.400.0",
48
+ "react": "^19.0.0",
49
+ "react-dom": "^19.0.0"
48
50
  },
49
51
  "devDependencies": {
50
- "@types/react": "^19.0.0",
51
- "@types/react-dom": "^19.0.0",
52
- "react": "^19.0.0",
53
- "react-dom": "^19.0.0",
54
- "@tanstack/react-query": "^5.40.0",
55
- "@tanstack/react-table": "^8.17.3",
56
52
  "@dnd-kit/core": "^6.0.0",
57
53
  "@dnd-kit/sortable": "^8.0.0",
58
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",
60
+ "@types/react": "^19.0.0",
61
+ "@types/react-dom": "^19.0.0",
62
+ "@vitejs/plugin-react": "^4.7.0",
63
+ "jsdom": "^25.0.1",
59
64
  "lucide-react": "^0.400.0",
65
+ "react": "^19.0.0",
66
+ "react-dom": "^19.0.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) */