@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 +1 -1
- package/dist/index.d.ts +25 -2
- package/dist/index.js +163 -83
- package/package.json +22 -14
- package/src/styles/components.css +9 -0
- package/src/styles/tokens.css +1 -1
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
|
|
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__ */
|
|
543
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
620
|
-
subtitle && /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
673
|
-
/* @__PURE__ */
|
|
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__ */
|
|
681
|
-
header.column.getIsSorted() === "desc" && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
807
|
+
/* @__PURE__ */ jsx17("span", { className: "flex-shrink-0 text-sm leading-5", children: s.icon }),
|
|
731
808
|
/* @__PURE__ */ jsxs13("div", { children: [
|
|
732
|
-
/* @__PURE__ */
|
|
733
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
803
|
-
/* @__PURE__ */
|
|
804
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
885
|
-
/* @__PURE__ */
|
|
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__ */
|
|
976
|
+
children: Array.from({ length: lineCount }, (_, i) => /* @__PURE__ */ jsx19("div", { children: i + 1 }, i))
|
|
900
977
|
}
|
|
901
978
|
),
|
|
902
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
1182
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1218
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
1314
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
1238
1315
|
function I({ d, size = 16, className, style }) {
|
|
1239
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1285
|
-
var IconUndo = (p) => /* @__PURE__ */
|
|
1286
|
-
var IconQuests = (p) => /* @__PURE__ */
|
|
1287
|
-
var IconTracks = (p) => /* @__PURE__ */
|
|
1288
|
-
var IconSettings = (p) => /* @__PURE__ */
|
|
1289
|
-
var IconChain = (p) => /* @__PURE__ */
|
|
1290
|
-
var IconPlus = (p) => /* @__PURE__ */
|
|
1291
|
-
var IconEdit = (p) => /* @__PURE__ */
|
|
1292
|
-
var IconTrash = (p) => /* @__PURE__ */
|
|
1293
|
-
var IconX = (p) => /* @__PURE__ */
|
|
1294
|
-
var IconCheck = (p) => /* @__PURE__ */
|
|
1295
|
-
var IconSearch = (p) => /* @__PURE__ */
|
|
1296
|
-
var IconChevronUp = (p) => /* @__PURE__ */
|
|
1297
|
-
var IconChevronDown = (p) => /* @__PURE__ */
|
|
1298
|
-
var IconChevronsDown = (p) => /* @__PURE__ */
|
|
1299
|
-
var IconChevronsRight = (p) => /* @__PURE__ */
|
|
1300
|
-
var IconArrowRight = (p) => /* @__PURE__ */
|
|
1301
|
-
var IconPlay = (p) => /* @__PURE__ */
|
|
1302
|
-
var IconStar = (p) => /* @__PURE__ */
|
|
1303
|
-
var IconDiamond = (p) => /* @__PURE__ */
|
|
1304
|
-
var IconCircle = (p) => /* @__PURE__ */
|
|
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.
|
|
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
|
-
"
|
|
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
|
+
}
|
package/src/styles/tokens.css
CHANGED
|
@@ -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) */
|