@astralui/core 0.1.2 → 0.1.4

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
@@ -14,6 +14,9 @@ dependency-free UI components. No CSS-in-JS runtime: just CSS variables +
14
14
  - 🖌️ **Per-brand theming** - `AstralThemeProvider` generates a brand palette from a single hex and injects per-scheme overrides at runtime (with live preview).
15
15
  - 🧩 **Components** - `AstralModal`, `AstralDrawer`, `AstralSelect`, `AstralMenu`, `AstralPinInput`, `AstralToaster` + `notifications`, `DateInput`, `Spinner`.
16
16
  - 🌌 **Backgrounds** - `SpaceBackground` (animated starfield) and `GridBackground` (masked technical grid) for auth screens, heroes, and splash pages.
17
+ - 📊 **Charts** - dependency-free, prop-driven `Sparkline`, `Delta`, `Donut`, `FunnelBars`, `StackedBars` (pure SVG + CSS, theme-aware).
18
+ - 🔐 **Auth surface** - `AuthShell` / `AuthCard` + `au-auth-*` classes for sign-in / sign-up / verify / onboarding screens.
19
+ - ❓ **ConfirmModal** - a confirmation/alert dialog (with a `danger` variant) built on `AstralModal`.
17
20
 
18
21
  ## Install
19
22
 
@@ -92,6 +95,12 @@ notifications.update({ id: 'x', message: 'Done', color: 'green', loading: false,
92
95
  | `notifications` + `AstralToaster` | Toast system. |
93
96
  | `SpaceBackground` | Animated cosmic backdrop with a twinkling starfield (wrap your content as children). |
94
97
  | `GridBackground` | Subtle technical grid with a brand-tinted glow, edge-masked. |
98
+ | `Sparkline` / `Delta` | Inline KPI sparkline; period-over-period delta badge. |
99
+ | `Donut` / `FunnelBars` / `StackedBars` | Interactive, linked-hover charts (pure SVG + CSS). |
100
+ | `AuthShell` / `AuthCard` | Centered auth/onboarding layout on a cosmic or wash backdrop. |
101
+ | `ConfirmModal` | Confirmation dialog (supports a `danger` variant). |
102
+ | `Avatar` | Image avatar with an initials fallback (brand gradient). |
103
+ | `StatusBadge` | Status pill (dot + label), prop-driven color + `filled`. |
95
104
  | `generateColors(hex)` | 10-shade palette from one color. |
96
105
 
97
106
  ## Token reference
package/dist/index.cjs CHANGED
@@ -613,6 +613,312 @@ function GridBackground({ children, className, style, fixed }) {
613
613
  const s = fixed ? { position: "fixed", inset: 0, ...style } : style ?? {};
614
614
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("astral-grid-bg", className), style: s, children });
615
615
  }
616
+ function chartColor(c) {
617
+ if (c.startsWith("var(") || c.startsWith("#") || c.startsWith("rgb") || c.startsWith("hsl")) return c;
618
+ return `var(--astral-color-${c.replace(".", "-")})`;
619
+ }
620
+ function Sparkline({ points, color }) {
621
+ if (!points || points.length < 2 || points.every((p) => p === 0)) return null;
622
+ const w = 100, h = 28;
623
+ const max = Math.max(...points), min = Math.min(...points);
624
+ const range = max - min || 1;
625
+ const step = w / (points.length - 1);
626
+ const d = points.map((p, i) => `${i === 0 ? "M" : "L"}${(i * step).toFixed(1)},${(h - (p - min) / range * h).toFixed(1)}`).join(" ");
627
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "au-spark", viewBox: `0 0 ${w} ${h}`, preserveAspectRatio: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d, fill: "none", stroke: chartColor(color), strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) });
628
+ }
629
+ function Delta({ current, previous }) {
630
+ if (previous == null || previous === 0) return null;
631
+ const pct = Math.round((current - previous) / previous * 100);
632
+ if (pct === 0) return null;
633
+ const up = pct > 0;
634
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `au-delta ${up ? "up" : "down"}`, children: [
635
+ up ? "\u25B2" : "\u25BC",
636
+ " ",
637
+ Math.abs(pct),
638
+ "%"
639
+ ] });
640
+ }
641
+ function Donut({ data, centerValue, centerLabel }) {
642
+ const [hovered, setHovered] = react.useState(null);
643
+ const total = data.reduce((s, d) => s + d.value, 0) || 1;
644
+ const c = 75, r = 58, sw = 18, circ = 2 * Math.PI * r;
645
+ let offset = 0;
646
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-donut", children: [
647
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-donut-ring", children: [
648
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 150, height: 150, viewBox: "0 0 150 150", children: [
649
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: c, cy: c, r, fill: "none", stroke: "var(--au-surface-2)", strokeWidth: sw }),
650
+ data.map((d, i) => {
651
+ const dash = d.value / total * circ;
652
+ const el = /* @__PURE__ */ jsxRuntime.jsx(
653
+ "circle",
654
+ {
655
+ cx: c,
656
+ cy: c,
657
+ r,
658
+ fill: "none",
659
+ stroke: chartColor(d.color),
660
+ strokeWidth: hovered === i ? sw + 4 : sw,
661
+ strokeDasharray: `${dash} ${circ - dash}`,
662
+ strokeDashoffset: -offset,
663
+ transform: `rotate(-90 ${c} ${c})`,
664
+ style: {
665
+ opacity: hovered == null || hovered === i ? 1 : 0.28,
666
+ transition: "opacity .15s, stroke-width .15s",
667
+ cursor: "pointer"
668
+ },
669
+ onMouseEnter: () => setHovered(i),
670
+ onMouseLeave: () => setHovered(null)
671
+ },
672
+ i
673
+ );
674
+ offset += dash;
675
+ return el;
676
+ })
677
+ ] }),
678
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-donut-ctr", children: [
679
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: centerValue }),
680
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: centerLabel })
681
+ ] })
682
+ ] }),
683
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-donut-legend", children: data.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs(
684
+ "div",
685
+ {
686
+ className: `au-dl ${hovered === i ? "on" : hovered != null ? "off" : ""}`,
687
+ onMouseEnter: () => setHovered(i),
688
+ onMouseLeave: () => setHovered(null),
689
+ children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dot", style: { background: chartColor(d.color) } }),
691
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: d.value.toLocaleString() }),
692
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dll", children: d.name })
693
+ ]
694
+ },
695
+ i
696
+ )) })
697
+ ] });
698
+ }
699
+ function FunnelBars({ data }) {
700
+ const [hov, setHov] = react.useState(null);
701
+ const max = Math.max(...data.map((d) => d.value), 1);
702
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-funnel", children: data.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs(
703
+ "div",
704
+ {
705
+ className: `au-fbar ${hov === i ? "on" : hov != null ? "off" : ""}`,
706
+ onMouseEnter: () => setHov(i),
707
+ onMouseLeave: () => setHov(null),
708
+ children: [
709
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-fbar-label", children: d.stage }),
710
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-fbar-track", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-fbar-fill", style: { width: `${d.value / max * 100}%`, background: chartColor(d.color) } }) }),
711
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-fbar-val", children: d.value.toLocaleString() })
712
+ ]
713
+ },
714
+ d.stage
715
+ )) });
716
+ }
717
+ function StackedBars({ rows, series }) {
718
+ const [hovSeries, setHovSeries] = react.useState(null);
719
+ const [hovSeg, setHovSeg] = react.useState(null);
720
+ const max = Math.max(...rows.map((r) => r.values.reduce((a, b) => a + b, 0)), 1);
721
+ const anyHover = hovSeries != null || hovSeg != null;
722
+ const active = (row, s) => hovSeries != null ? s === hovSeries : hovSeg != null ? hovSeg.row === row && hovSeg.s === s : true;
723
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-usage", children: [
724
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-usage-legend", children: series.map((s, si) => /* @__PURE__ */ jsxRuntime.jsxs(
725
+ "div",
726
+ {
727
+ className: `au-ulg ${hovSeries === si ? "on" : hovSeries != null ? "off" : ""}`,
728
+ onMouseEnter: () => setHovSeries(si),
729
+ onMouseLeave: () => setHovSeries(null),
730
+ children: [
731
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dot", style: { background: chartColor(s.color) } }),
732
+ s.label
733
+ ]
734
+ },
735
+ si
736
+ )) }),
737
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-usage-rows", children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `au-urow ${hovSeg?.row === i ? "on" : ""}`, children: [
738
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-urow-label", title: r.label, children: r.label }),
739
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-utrack", children: r.values.map((v, si) => /* @__PURE__ */ jsxRuntime.jsx(
740
+ "div",
741
+ {
742
+ className: "au-useg",
743
+ onMouseEnter: () => setHovSeg({ row: i, s: si }),
744
+ onMouseLeave: () => setHovSeg(null),
745
+ style: {
746
+ width: `${v / max * 100}%`,
747
+ background: chartColor(series[si].color),
748
+ opacity: anyHover && !active(i, si) ? 0.2 : 1,
749
+ filter: anyHover && active(i, si) ? "brightness(1.2)" : void 0
750
+ }
751
+ },
752
+ si
753
+ )) }),
754
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-unums", children: r.values.map((v, si) => /* @__PURE__ */ jsxRuntime.jsx(
755
+ "b",
756
+ {
757
+ style: {
758
+ color: chartColor(series[si].color),
759
+ opacity: anyHover && !active(i, si) ? 0.28 : 1,
760
+ textShadow: anyHover && active(i, si) ? `0 0 10px ${chartColor(series[si].color)}` : void 0
761
+ },
762
+ children: v.toLocaleString()
763
+ },
764
+ si
765
+ )) })
766
+ ] }, i)) })
767
+ ] });
768
+ }
769
+ function AuthShell({ children, backdrop = "space", topLeft, topRight, className, style }) {
770
+ const cls2 = `au-auth${backdrop === "space" ? " astral-space-bg" : ""}${className ? " " + className : ""}`;
771
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls2, style, children: [
772
+ backdrop === "space" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "astral-stars", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-auth-bg", "aria-hidden": "true" }),
773
+ topLeft,
774
+ topRight ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-auth-lang", children: topRight }) : null,
775
+ children
776
+ ] });
777
+ }
778
+ function AuthCard({ children, banner, className, style }) {
779
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `au-auth-card${className ? " " + className : ""}`, style, children: [
780
+ banner ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-auth-banner", children: banner }) : null,
781
+ children
782
+ ] });
783
+ }
784
+ function ConfirmModal({
785
+ opened,
786
+ onClose,
787
+ onConfirm,
788
+ title,
789
+ message,
790
+ confirmLabel = "Confirm",
791
+ cancelLabel = "Cancel",
792
+ danger,
793
+ loading,
794
+ width = 420,
795
+ children
796
+ }) {
797
+ return /* @__PURE__ */ jsxRuntime.jsx(AstralModal, { opened, onClose, title, width, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "clay-form", children: [
798
+ message != null && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, lineHeight: 1.55, color: "var(--au-dim)" }, children: message }),
799
+ children,
800
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "clay-actions", children: [
801
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-btn", onClick: onClose, disabled: loading, children: cancelLabel }),
802
+ /* @__PURE__ */ jsxRuntime.jsxs(
803
+ "button",
804
+ {
805
+ type: "button",
806
+ className: `au-btn ${danger ? "solidred" : "primary"}`,
807
+ onClick: onConfirm,
808
+ disabled: loading,
809
+ children: [
810
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-spinner", style: { width: 14, height: 14 } }) : null,
811
+ confirmLabel
812
+ ]
813
+ }
814
+ )
815
+ ] })
816
+ ] }) });
817
+ }
818
+ function Avatar({ url, name, size = 36, radius = "50%", className = "", overlay = false, color }) {
819
+ const [broken, setBroken] = react.useState(false);
820
+ react.useEffect(() => {
821
+ setBroken(false);
822
+ }, [url]);
823
+ if (overlay) {
824
+ if (!url || broken) return null;
825
+ return /* @__PURE__ */ jsxRuntime.jsx(
826
+ "img",
827
+ {
828
+ src: url,
829
+ alt: name || "",
830
+ className,
831
+ referrerPolicy: "no-referrer",
832
+ onError: () => setBroken(true),
833
+ style: { position: "absolute", inset: 0, width: "100%", height: "100%", borderRadius: radius, objectFit: "cover" }
834
+ }
835
+ );
836
+ }
837
+ if (url && !broken) {
838
+ return /* @__PURE__ */ jsxRuntime.jsx(
839
+ "img",
840
+ {
841
+ src: url,
842
+ alt: name || "",
843
+ className,
844
+ width: size,
845
+ height: size,
846
+ referrerPolicy: "no-referrer",
847
+ loading: "lazy",
848
+ onError: () => setBroken(true),
849
+ style: { width: size, height: size, borderRadius: radius, objectFit: "cover", display: "block", flex: "0 0 auto" }
850
+ }
851
+ );
852
+ }
853
+ const initials = (name || "").trim().slice(0, 2).toUpperCase() || "-";
854
+ return /* @__PURE__ */ jsxRuntime.jsx(
855
+ "span",
856
+ {
857
+ className,
858
+ "aria-hidden": true,
859
+ style: {
860
+ width: size,
861
+ height: size,
862
+ borderRadius: radius,
863
+ flex: "0 0 auto",
864
+ display: "inline-grid",
865
+ placeItems: "center",
866
+ color: "#fff",
867
+ fontWeight: 700,
868
+ fontSize: Math.max(9, Math.round(size * 0.4)),
869
+ lineHeight: 1,
870
+ background: color ?? "linear-gradient(135deg, var(--astral-color-violet-6), var(--astral-color-violet-8))"
871
+ },
872
+ children: initials
873
+ }
874
+ );
875
+ }
876
+ var COLOR_MAP = {
877
+ offline: "#3b82f6",
878
+ online: "#16a34a",
879
+ active: "#16a34a",
880
+ suspended: "#eab308",
881
+ banned: "#ef4444",
882
+ creating: "#06b6d4",
883
+ verification_needed: "#EF8E2E",
884
+ signup_failed: "#ef4444",
885
+ untested: "#9ca3af",
886
+ failed: "#ef4444",
887
+ idle: "#9ca3af",
888
+ starting: "#06b6d4",
889
+ running: "#16a34a",
890
+ stopping: "#eab308",
891
+ stopped: "#EF8E2E",
892
+ error: "#ef4444",
893
+ draft: "#9ca3af",
894
+ scheduled: "#3b82f6",
895
+ paused: "#eab308",
896
+ completed: "#16a34a",
897
+ skipped: "#EF8E2E",
898
+ in_campaign: "#8b5cf6",
899
+ in_progress: "#3b82f6",
900
+ pending: "#9ca3af",
901
+ replied: "#8b5cf6",
902
+ success: "#16a34a"
903
+ };
904
+ var FILLED = /* @__PURE__ */ new Set(["running", "active", "online"]);
905
+ function StatusBadge({ status, label, color, filled }) {
906
+ const c = color ?? COLOR_MAP[status] ?? "#9ca3af";
907
+ const isFilled = filled ?? FILLED.has(status);
908
+ const text = label ?? status;
909
+ return /* @__PURE__ */ jsxRuntime.jsxs(
910
+ "span",
911
+ {
912
+ className: `au-sbadge${isFilled ? " filled" : ""}`,
913
+ style: { "--au-sb": c },
914
+ title: typeof text === "string" ? text : void 0,
915
+ children: [
916
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-sbadge-dot" }),
917
+ text
918
+ ]
919
+ }
920
+ );
921
+ }
616
922
 
617
923
  exports.AstralDrawer = AstralDrawer;
618
924
  exports.AstralMenu = AstralMenu;
@@ -621,12 +927,23 @@ exports.AstralPinInput = AstralPinInput;
621
927
  exports.AstralSelect = AstralSelect;
622
928
  exports.AstralThemeProvider = AstralThemeProvider;
623
929
  exports.AstralToaster = AstralToaster;
930
+ exports.AuthCard = AuthCard;
931
+ exports.AuthShell = AuthShell;
932
+ exports.Avatar = Avatar;
624
933
  exports.ColorSchemeProvider = ColorSchemeProvider;
934
+ exports.ConfirmModal = ConfirmModal;
625
935
  exports.DateInput = DateInput;
936
+ exports.Delta = Delta;
937
+ exports.Donut = Donut;
938
+ exports.FunnelBars = FunnelBars;
626
939
  exports.GridBackground = GridBackground;
627
940
  exports.SpaceBackground = SpaceBackground;
941
+ exports.Sparkline = Sparkline;
628
942
  exports.Spinner = Spinner2;
943
+ exports.StackedBars = StackedBars;
944
+ exports.StatusBadge = StatusBadge;
629
945
  exports.buildOrgCss = buildOrgCss;
946
+ exports.chartColor = chartColor;
630
947
  exports.dateTimeToInputStr = dateTimeToInputStr;
631
948
  exports.dateToInputStr = dateToInputStr;
632
949
  exports.generateColors = generateColors;