@tangle-network/sandbox-ui 0.16.0 → 0.16.1

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.
@@ -666,7 +666,7 @@ function NewSandboxCard({ onClick, className }) {
666
666
  }
667
667
 
668
668
  // src/dashboard/sandbox-table.tsx
669
- import { Terminal as Terminal2, Code2 as Code22, Key, Trash2 as Trash22, RefreshCw, ChevronLeft, ChevronRight, Users as Users2, User } from "lucide-react";
669
+ import { Terminal as Terminal2, Code2 as Code22, Key, Trash2 as Trash22, RefreshCw, ChevronLeft, ChevronRight, Users as Users2, User, Play as Play2 } from "lucide-react";
670
670
  import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
671
671
  var statusColors = {
672
672
  running: { dot: "bg-[var(--code-success)] animate-pulse", text: "text-[var(--code-success)]", bar: "bg-[var(--code-success)]" },
@@ -677,6 +677,9 @@ var statusColors = {
677
677
  failed: { dot: "bg-[var(--code-error)]", text: "text-[var(--code-error)]", bar: "bg-[var(--code-error)]" },
678
678
  archived: { dot: "bg-border", text: "text-muted-foreground", bar: "bg-border" }
679
679
  };
680
+ function isResumable(status) {
681
+ return status !== "running" && status !== "provisioning" && status !== "creating";
682
+ }
680
683
  function MiniMeter({ label, percent, className }) {
681
684
  return /* @__PURE__ */ jsxs6("div", { className: cn("space-y-1", className), children: [
682
685
  /* @__PURE__ */ jsxs6("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground", children: [
@@ -698,6 +701,7 @@ function SandboxTable({
698
701
  onOpenIDE,
699
702
  onOpenTerminal,
700
703
  onSSH,
704
+ onResume,
701
705
  onWake,
702
706
  onMore,
703
707
  onDelete,
@@ -706,6 +710,19 @@ function SandboxTable({
706
710
  const totalCount = total ?? sandboxes.length;
707
711
  const totalPages = Math.ceil(totalCount / pageSize);
708
712
  const hasTeamSandboxes = sandboxes.some((sb) => sb.team !== void 0);
713
+ const resolveResumeHandler = (status) => {
714
+ if (onResume) return onResume;
715
+ if (status === "hibernating") return onWake;
716
+ return void 0;
717
+ };
718
+ const resolveRowClick = (sb) => {
719
+ if (sb.status === "running") {
720
+ return onOpenIDE ? () => onOpenIDE(sb.id) : void 0;
721
+ }
722
+ if (!isResumable(sb.status)) return void 0;
723
+ const handler = resolveResumeHandler(sb.status);
724
+ return handler ? () => handler(sb.id) : void 0;
725
+ };
709
726
  return /* @__PURE__ */ jsxs6("div", { className: cn("w-full", className), children: [
710
727
  /* @__PURE__ */ jsx7("div", { className: "w-full bg-card rounded-2xl overflow-hidden border border-border", children: /* @__PURE__ */ jsx7("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs6("table", { className: "w-full text-left border-collapse", children: [
711
728
  /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs6("tr", { className: "bg-background border-b border-border", children: [
@@ -721,65 +738,124 @@ function SandboxTable({
721
738
  const isActive = sb.status === "running";
722
739
  const isHibernating = sb.status === "hibernating";
723
740
  const isProvisioning = sb.status === "provisioning";
724
- return /* @__PURE__ */ jsxs6("tr", { className: "hover:bg-muted/50 transition-colors group relative", children: [
725
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 whitespace-nowrap", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
726
- /* @__PURE__ */ jsx7("span", { className: cn("flex h-2.5 w-2.5 rounded-full", sc.dot) }),
727
- /* @__PURE__ */ jsx7("span", { className: cn("text-xs font-bold uppercase tracking-wide", sc.text), children: sb.status.charAt(0).toUpperCase() + sb.status.slice(1) })
728
- ] }) }),
729
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
730
- /* @__PURE__ */ jsx7("span", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors", children: sb.name }),
731
- sb.nodeId && /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground", children: sb.nodeId })
732
- ] }) }),
733
- hasTeamSandboxes && /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: sb.team ? /* @__PURE__ */ jsxs6(
734
- "div",
741
+ const resumeHandler = isResumable(sb.status) ? resolveResumeHandler(sb.status) : void 0;
742
+ const onRowClick = resolveRowClick(sb);
743
+ const resumeLabel = isHibernating ? "Wake Up" : "Resume";
744
+ const stopRowClick = (e) => e.stopPropagation();
745
+ return (
746
+ // onClick is a sighted-user convenience only. We
747
+ // deliberately do NOT add role="button" / tabIndex /
748
+ // an onKeyDown handler to the row overriding a
749
+ // <tr>'s implicit row role with "button" collapses
750
+ // the per-cell announcements (Status, Environment,
751
+ // Resources…) that screen-reader users navigate
752
+ // through. Keyboard and assistive-tech users reach
753
+ // the same actions through the real <button>
754
+ // elements inside the actions cell (Resume, Open
755
+ // IDE, Delete, …), which keep their native
756
+ // semantics. Mouse users get the click-anywhere
757
+ // affordance; nobody loses access.
758
+ /* @__PURE__ */ jsxs6(
759
+ "tr",
735
760
  {
736
- className: "inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-semibold text-[var(--accent-text)]",
737
- title: `Shared with ${sb.team.name ?? "Team"} \xB7 ${sb.team.role}`,
738
- children: [
739
- /* @__PURE__ */ jsx7(Users2, { className: "h-3 w-3", "aria-hidden": "true" }),
740
- /* @__PURE__ */ jsx7("span", { children: sb.team.name ?? "Team" }),
741
- /* @__PURE__ */ jsxs6("span", { className: "font-normal text-muted-foreground", children: [
742
- "\xB7 ",
743
- sb.team.role
744
- ] })
745
- ]
746
- }
747
- ) : /* @__PURE__ */ jsxs6(
748
- "div",
749
- {
750
- className: "inline-flex items-center gap-1.5 rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium text-muted-foreground",
751
- title: "Personal sandbox",
761
+ className: cn(
762
+ "group relative transition-colors",
763
+ onRowClick ? "cursor-pointer hover:bg-muted/50" : "hover:bg-muted/30"
764
+ ),
765
+ onClick: onRowClick,
752
766
  children: [
753
- /* @__PURE__ */ jsx7(User, { className: "h-3 w-3", "aria-hidden": "true" }),
754
- "Personal"
767
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 whitespace-nowrap", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
768
+ /* @__PURE__ */ jsx7("span", { className: cn("flex h-2.5 w-2.5 rounded-full", sc.dot) }),
769
+ /* @__PURE__ */ jsx7("span", { className: cn("text-xs font-bold uppercase tracking-wide", sc.text), children: sb.status.charAt(0).toUpperCase() + sb.status.slice(1) })
770
+ ] }) }),
771
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
772
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors", children: sb.name }),
773
+ sb.nodeId && /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground", children: sb.nodeId })
774
+ ] }) }),
775
+ hasTeamSandboxes && /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: sb.team ? /* @__PURE__ */ jsxs6(
776
+ "div",
777
+ {
778
+ className: "inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-semibold text-[var(--accent-text)]",
779
+ title: `Shared with ${sb.team.name ?? "Team"} \xB7 ${sb.team.role}`,
780
+ children: [
781
+ /* @__PURE__ */ jsx7(Users2, { className: "h-3 w-3", "aria-hidden": "true" }),
782
+ /* @__PURE__ */ jsx7("span", { children: sb.team.name ?? "Team" }),
783
+ /* @__PURE__ */ jsxs6("span", { className: "font-normal text-muted-foreground", children: [
784
+ "\xB7 ",
785
+ sb.team.role
786
+ ] })
787
+ ]
788
+ }
789
+ ) : /* @__PURE__ */ jsxs6(
790
+ "div",
791
+ {
792
+ className: "inline-flex items-center gap-1.5 rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium text-muted-foreground",
793
+ title: "Personal sandbox",
794
+ children: [
795
+ /* @__PURE__ */ jsx7(User, { className: "h-3 w-3", "aria-hidden": "true" }),
796
+ "Personal"
797
+ ]
798
+ }
799
+ ) }),
800
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
801
+ sb.imageIcon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center", children: sb.imageIcon }),
802
+ sb.image && /* @__PURE__ */ jsx7("span", { className: "text-xs font-bold text-foreground", children: sb.image })
803
+ ] }) }),
804
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: isActive ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48", children: [
805
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: sb.cpuPercent ?? 0 }),
806
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: sb.ramTotal ? Math.round((sb.ramUsed ?? 0) / sb.ramTotal * 100) : 0 })
807
+ ] }) : isProvisioning ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-primary italic text-[10px] font-bold", children: [
808
+ /* @__PURE__ */ jsx7(RefreshCw, { className: "h-3.5 w-3.5 animate-spin" }),
809
+ sb.provisioningMessage ?? "Allocating nodes..."
810
+ ] }) : isHibernating ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48 opacity-30", children: [
811
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: 0 }),
812
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: 0 })
813
+ ] }) : null }),
814
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1", children: [
815
+ isActive && /* @__PURE__ */ jsxs6(Fragment4, { children: [
816
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
817
+ stopRowClick(e);
818
+ onOpenIDE?.(sb.id);
819
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Open IDE", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
820
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
821
+ stopRowClick(e);
822
+ onOpenTerminal?.(sb.id);
823
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Terminal", children: /* @__PURE__ */ jsx7(Terminal2, { className: "h-4 w-4" }) }),
824
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
825
+ stopRowClick(e);
826
+ onSSH?.(sb.id);
827
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "SSH", children: /* @__PURE__ */ jsx7(Key, { className: "h-4 w-4" }) })
828
+ ] }),
829
+ resumeHandler && /* @__PURE__ */ jsxs6(
830
+ "button",
831
+ {
832
+ type: "button",
833
+ onClick: (e) => {
834
+ stopRowClick(e);
835
+ resumeHandler(sb.id);
836
+ },
837
+ className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-primary text-[10px] font-bold uppercase tracking-wider hover:bg-[var(--accent-surface-soft)] active:scale-95 transition-all",
838
+ title: resumeLabel,
839
+ children: [
840
+ /* @__PURE__ */ jsx7(Play2, { className: "h-3 w-3" }),
841
+ resumeLabel
842
+ ]
843
+ }
844
+ ),
845
+ onMore && /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
846
+ stopRowClick(e);
847
+ onMore(sb.id);
848
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
849
+ onDelete && canAdminSandbox(sb) && /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
850
+ stopRowClick(e);
851
+ onDelete(sb.id);
852
+ }, className: "p-2 rounded-lg hover:bg-[var(--surface-danger-bg)] text-muted-foreground hover:text-[var(--surface-danger-text)] transition-all active:scale-90", title: "Delete", children: /* @__PURE__ */ jsx7(Trash22, { className: "h-4 w-4" }) })
853
+ ] }) })
755
854
  ]
756
- }
757
- ) }),
758
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
759
- sb.imageIcon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center", children: sb.imageIcon }),
760
- sb.image && /* @__PURE__ */ jsx7("span", { className: "text-xs font-bold text-foreground", children: sb.image })
761
- ] }) }),
762
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: isActive ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48", children: [
763
- /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: sb.cpuPercent ?? 0 }),
764
- /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: sb.ramTotal ? Math.round((sb.ramUsed ?? 0) / sb.ramTotal * 100) : 0 })
765
- ] }) : isProvisioning ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-primary italic text-[10px] font-bold", children: [
766
- /* @__PURE__ */ jsx7(RefreshCw, { className: "h-3.5 w-3.5 animate-spin" }),
767
- sb.provisioningMessage ?? "Allocating nodes..."
768
- ] }) : isHibernating ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48 opacity-30", children: [
769
- /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: 0 }),
770
- /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: 0 })
771
- ] }) : null }),
772
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1", children: [
773
- isActive && /* @__PURE__ */ jsxs6(Fragment4, { children: [
774
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onOpenIDE?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Open IDE", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
775
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onOpenTerminal?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Terminal", children: /* @__PURE__ */ jsx7(Terminal2, { className: "h-4 w-4" }) }),
776
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onSSH?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "SSH", children: /* @__PURE__ */ jsx7(Key, { className: "h-4 w-4" }) })
777
- ] }),
778
- isHibernating && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onWake?.(sb.id), className: "px-3 py-1.5 rounded-lg border border-border text-primary text-[10px] font-bold uppercase tracking-wider hover:bg-[var(--accent-surface-soft)] active:scale-95 transition-all", children: "Wake Up" }),
779
- onMore && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onMore(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
780
- onDelete && canAdminSandbox(sb) && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onDelete(sb.id), className: "p-2 rounded-lg hover:bg-[var(--surface-danger-bg)] text-muted-foreground hover:text-[var(--surface-danger-text)] transition-all active:scale-90", title: "Delete", children: /* @__PURE__ */ jsx7(Trash22, { className: "h-4 w-4" }) })
781
- ] }) })
782
- ] }, sb.id);
855
+ },
856
+ sb.id
857
+ )
858
+ );
783
859
  }) })
784
860
  ] }) }) }),
785
861
  totalPages > 1 && /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex flex-col md:flex-row justify-between items-center text-muted-foreground text-xs font-medium gap-4", children: [
@@ -231,12 +231,28 @@ interface SandboxTableProps {
231
231
  onOpenIDE?: (id: string) => void;
232
232
  onOpenTerminal?: (id: string) => void;
233
233
  onSSH?: (id: string) => void;
234
+ /**
235
+ * Resume a stopped / failed / archived sandbox, or wake a hibernating
236
+ * one. Surfaces an explicit "Resume" (or "Wake Up" for `hibernating`)
237
+ * button in the actions cell and makes the row body clickable for any
238
+ * status this prop covers. Preferred over `onWake` for new code — the
239
+ * two are kept as separate props only so existing consumers that
240
+ * wired up `onWake` for hibernating continue to work.
241
+ */
242
+ onResume?: (id: string) => void;
243
+ /**
244
+ * @deprecated Use `onResume` instead. Retained for back-compat:
245
+ * when `onResume` is not provided but `onWake` is, hibernating rows
246
+ * will still surface a Wake action. New consumers should pass
247
+ * `onResume` so stopped / failed / archived rows also get a start
248
+ * affordance.
249
+ */
234
250
  onWake?: (id: string) => void;
235
251
  onMore?: (id: string) => void;
236
252
  onDelete?: (id: string) => void;
237
253
  className?: string;
238
254
  }
239
- declare function SandboxTable({ sandboxes, page, pageSize, total, onPageChange, onOpenIDE, onOpenTerminal, onSSH, onWake, onMore, onDelete, className, }: SandboxTableProps): react_jsx_runtime.JSX.Element;
255
+ declare function SandboxTable({ sandboxes, page, pageSize, total, onPageChange, onOpenIDE, onOpenTerminal, onSSH, onResume, onWake, onMore, onDelete, className, }: SandboxTableProps): react_jsx_runtime.JSX.Element;
240
256
 
241
257
  interface Invoice {
242
258
  id: string;
package/dist/dashboard.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  VariantList,
44
44
  canAdminSandbox,
45
45
  useSidebar
46
- } from "./chunk-HLZTKSGT.js";
46
+ } from "./chunk-X3UATIZH.js";
47
47
  import {
48
48
  BillingDashboard,
49
49
  InfoPanel,
package/dist/globals.css CHANGED
@@ -232,6 +232,9 @@
232
232
  .pointer-events-none {
233
233
  pointer-events: none;
234
234
  }
235
+ .collapse {
236
+ visibility: collapse;
237
+ }
235
238
  .invisible {
236
239
  visibility: hidden;
237
240
  }
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ import {
174
174
  VariantList,
175
175
  canAdminSandbox,
176
176
  useSidebar
177
- } from "./chunk-HLZTKSGT.js";
177
+ } from "./chunk-X3UATIZH.js";
178
178
  import {
179
179
  BillingDashboard,
180
180
  InfoPanel,
package/dist/styles.css CHANGED
@@ -232,6 +232,9 @@
232
232
  .pointer-events-none {
233
233
  pointer-events: none;
234
234
  }
235
+ .collapse {
236
+ visibility: collapse;
237
+ }
235
238
  .invisible {
236
239
  visibility: hidden;
237
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",