@stigmer/react 3.4.1 → 3.5.0
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/agent/AgentDetailView.d.ts +14 -4
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +73 -11
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/internal/agentToInput.d.ts +3 -0
- package/agent/internal/agentToInput.d.ts.map +1 -1
- package/agent/internal/agentToInput.js +21 -1
- package/agent/internal/agentToInput.js.map +1 -1
- package/api-key/ApiKeyListPanel.d.ts +8 -1
- package/api-key/ApiKeyListPanel.d.ts.map +1 -1
- package/api-key/ApiKeyListPanel.js +9 -7
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +8 -0
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/dependency-graph/types.d.ts +20 -8
- package/dependency-graph/types.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.js +21 -1
- package/dependency-graph/useDependencyGraph.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +4 -0
- package/index.js.map +1 -1
- package/internal/scroll-area.d.ts +4 -0
- package/internal/scroll-area.d.ts.map +1 -0
- package/internal/scroll-area.js +15 -0
- package/internal/scroll-area.js.map +1 -0
- package/internal/tooltip.d.ts +7 -0
- package/internal/tooltip.d.ts.map +1 -0
- package/internal/tooltip.js +31 -0
- package/internal/tooltip.js.map +1 -0
- package/mcp-server/StdioSandboxNotice.d.ts +12 -11
- package/mcp-server/StdioSandboxNotice.d.ts.map +1 -1
- package/mcp-server/StdioSandboxNotice.js +13 -12
- package/mcp-server/StdioSandboxNotice.js.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.js +1 -1
- package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
- package/package.json +4 -4
- package/resource-creation/templates/mcp-server-templates.d.ts.map +1 -1
- package/resource-creation/templates/mcp-server-templates.js +17 -14
- package/resource-creation/templates/mcp-server-templates.js.map +1 -1
- package/sidebar/SettingsSidebar.d.ts +62 -0
- package/sidebar/SettingsSidebar.d.ts.map +1 -0
- package/sidebar/SettingsSidebar.js +60 -0
- package/sidebar/SettingsSidebar.js.map +1 -0
- package/sidebar/WorkspaceSidebar.d.ts +87 -0
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -0
- package/sidebar/WorkspaceSidebar.js +95 -0
- package/sidebar/WorkspaceSidebar.js.map +1 -0
- package/sidebar/chrome.d.ts +48 -0
- package/sidebar/chrome.d.ts.map +1 -0
- package/sidebar/chrome.js +32 -0
- package/sidebar/chrome.js.map +1 -0
- package/sidebar/index.d.ts +6 -0
- package/sidebar/index.d.ts.map +1 -0
- package/sidebar/index.js +9 -0
- package/sidebar/index.js.map +1 -0
- package/sidebar/types.d.ts +54 -0
- package/sidebar/types.d.ts.map +1 -0
- package/sidebar/types.js +2 -0
- package/sidebar/types.js.map +1 -0
- package/src/agent/AgentDetailView.tsx +198 -11
- package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +166 -0
- package/src/agent/internal/__tests__/agentToInput.test.ts +154 -0
- package/src/agent/internal/agentToInput.ts +25 -7
- package/src/api-key/ApiKeyListPanel.tsx +17 -4
- package/src/dependency-graph/DependencyTreeNode.tsx +8 -0
- package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +108 -0
- package/src/dependency-graph/types.ts +20 -8
- package/src/dependency-graph/useDependencyGraph.ts +23 -1
- package/src/index.ts +13 -0
- package/src/internal/scroll-area.tsx +37 -0
- package/src/internal/tooltip.tsx +66 -0
- package/src/mcp-server/StdioSandboxNotice.tsx +16 -14
- package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +3 -2
- package/src/mcp-server/steps/IdentityTransportStep.tsx +7 -0
- package/src/resource-creation/templates/mcp-server-templates.ts +17 -14
- package/src/sidebar/SettingsSidebar.tsx +173 -0
- package/src/sidebar/WorkspaceSidebar.tsx +386 -0
- package/src/sidebar/__tests__/SettingsSidebar.test.tsx +107 -0
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +236 -0
- package/src/sidebar/chrome.tsx +129 -0
- package/src/sidebar/index.ts +16 -0
- package/src/sidebar/types.ts +55 -0
- package/src/test/__tests__/samples.test.ts +2 -0
- package/src/test/samples.ts +48 -0
- package/styles.css +1 -1
- package/test/samples.d.ts +21 -0
- package/test/samples.d.ts.map +1 -1
- package/test/samples.js +32 -0
- package/test/samples.js.map +1 -1
|
@@ -41,10 +41,12 @@ export function useDependencyGraph({
|
|
|
41
41
|
if (!spec) return { tree: null, isEmpty: true };
|
|
42
42
|
|
|
43
43
|
const { mcpServerUsages, skillRefs, subAgents } = spec;
|
|
44
|
+
const datastoreUsages = spec.datastoreUsages ?? [];
|
|
44
45
|
|
|
45
46
|
const hasDeps =
|
|
46
47
|
mcpServerUsages.length > 0 ||
|
|
47
48
|
skillRefs.length > 0 ||
|
|
49
|
+
datastoreUsages.length > 0 ||
|
|
48
50
|
subAgents.length > 0;
|
|
49
51
|
|
|
50
52
|
if (!hasDeps) return { tree: null, isEmpty: true };
|
|
@@ -92,6 +94,24 @@ export function useDependencyGraph({
|
|
|
92
94
|
};
|
|
93
95
|
});
|
|
94
96
|
|
|
97
|
+
const datastoreNodes: DependencyNode[] = datastoreUsages
|
|
98
|
+
.filter((u) => u.datastoreRef)
|
|
99
|
+
.map((usage) => {
|
|
100
|
+
nodeCount++;
|
|
101
|
+
const ref = usage.datastoreRef!;
|
|
102
|
+
return {
|
|
103
|
+
id: `datastore:${ref.slug}`,
|
|
104
|
+
kind: "datastore" as const,
|
|
105
|
+
label: ref.slug,
|
|
106
|
+
qualifiedLabel:
|
|
107
|
+
ref.org && ref.org !== agentOrg
|
|
108
|
+
? `${ref.org}/${ref.slug}`
|
|
109
|
+
: undefined,
|
|
110
|
+
children: [],
|
|
111
|
+
ref: { org: ref.org || agentOrg, slug: ref.slug },
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
95
115
|
const subAgentNodes: DependencyNode[] = subAgents.map((sa) => {
|
|
96
116
|
nodeCount++;
|
|
97
117
|
|
|
@@ -143,11 +163,13 @@ export function useDependencyGraph({
|
|
|
143
163
|
};
|
|
144
164
|
});
|
|
145
165
|
|
|
166
|
+
// Child ordering mirrors the Overview sections: MCP servers,
|
|
167
|
+
// skills, datastores, then sub-agents.
|
|
146
168
|
const root: DependencyNode = {
|
|
147
169
|
id: `agent:${agentName}`,
|
|
148
170
|
kind: "agent",
|
|
149
171
|
label: agentName,
|
|
150
|
-
children: [...mcpNodes, ...skillNodes, ...subAgentNodes],
|
|
172
|
+
children: [...mcpNodes, ...skillNodes, ...datastoreNodes, ...subAgentNodes],
|
|
151
173
|
};
|
|
152
174
|
|
|
153
175
|
return {
|
package/src/index.ts
CHANGED
|
@@ -1050,6 +1050,19 @@ export { UsageSection } from "./settings/index.js";
|
|
|
1050
1050
|
export { UserMenu } from "./user/index.js";
|
|
1051
1051
|
export type { UserMenuProps } from "./user/index.js";
|
|
1052
1052
|
|
|
1053
|
+
// Sidebar — the console's navigation chrome (workspace + settings zones).
|
|
1054
|
+
// The web console, desktop app, and documentation tours all render these;
|
|
1055
|
+
// hosts inject routing via `renderLink` and identity via the footer slot.
|
|
1056
|
+
export { WorkspaceSidebar, SettingsSidebar } from "./sidebar/index.js";
|
|
1057
|
+
export type {
|
|
1058
|
+
WorkspaceSidebarProps,
|
|
1059
|
+
WorkspaceSidebarActivity,
|
|
1060
|
+
WorkspaceNavId,
|
|
1061
|
+
SettingsSidebarProps,
|
|
1062
|
+
SidebarLinkRenderProps,
|
|
1063
|
+
RenderSidebarLink,
|
|
1064
|
+
} from "./sidebar/index.js";
|
|
1065
|
+
|
|
1053
1066
|
// API Key — data hooks, behavior hooks, and styled components for API key lifecycle
|
|
1054
1067
|
export {
|
|
1055
1068
|
useApiKeyList,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// SDK-internal thin-scrollbar scroll container.
|
|
8
|
+
//
|
|
9
|
+
// NOT exported from @stigmer/react. Plain CSS (no primitive library): a thin
|
|
10
|
+
// token-colored scrollbar over native overflow, so long lists (sidebar
|
|
11
|
+
// recents) scroll like the console everywhere the SDK renders.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function ScrollArea({
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentPropsWithoutRef<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={cn(
|
|
22
|
+
"relative overflow-auto",
|
|
23
|
+
"[scrollbar-width:thin] [scrollbar-color:var(--color-border)_transparent]",
|
|
24
|
+
"[&::-webkit-scrollbar]:w-2.5",
|
|
25
|
+
"[&::-webkit-scrollbar-track]:bg-transparent",
|
|
26
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
|
27
|
+
"[&::-webkit-scrollbar-thumb]:bg-border",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { ScrollArea };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
import { useStigmerPortalContainer } from "../portal-container.js";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// SDK-internal styled Tooltip primitives over @base-ui/react.
|
|
10
|
+
//
|
|
11
|
+
// Like ./menu.tsx, these are NOT exported from @stigmer/react. They give SDK
|
|
12
|
+
// styled components (WorkspaceSidebar recents rows, and any future hover
|
|
13
|
+
// hints) one source of truth for tooltip styling, portaled into the Stigmer
|
|
14
|
+
// theme scope so tokens resolve in embeds.
|
|
15
|
+
//
|
|
16
|
+
// Portaled content uses popover-* / main-area tokens per DD-005; the ring
|
|
17
|
+
// follows the menu popup's established `ring-foreground/10` hairline.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function TooltipProvider(props: TooltipPrimitive.Provider.Props) {
|
|
21
|
+
return <TooltipPrimitive.Provider {...props} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Tooltip(props: TooltipPrimitive.Root.Props) {
|
|
25
|
+
return <TooltipPrimitive.Root {...props} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) {
|
|
29
|
+
return <TooltipPrimitive.Trigger {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TooltipContent({
|
|
33
|
+
side = "right",
|
|
34
|
+
sideOffset = 8,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: TooltipPrimitive.Popup.Props &
|
|
39
|
+
Pick<TooltipPrimitive.Positioner.Props, "side" | "sideOffset">) {
|
|
40
|
+
const portalContainer = useStigmerPortalContainer();
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<TooltipPrimitive.Portal container={portalContainer}>
|
|
44
|
+
<TooltipPrimitive.Positioner
|
|
45
|
+
className="isolate z-50 outline-none"
|
|
46
|
+
side={side}
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
>
|
|
49
|
+
<TooltipPrimitive.Popup
|
|
50
|
+
className={cn(
|
|
51
|
+
"bg-popover text-popover-foreground ring-foreground/10 z-50 max-w-64 rounded-lg px-2.5 py-1.5 text-sm shadow-md ring-1",
|
|
52
|
+
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95",
|
|
53
|
+
"data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
54
|
+
"duration-100",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</TooltipPrimitive.Popup>
|
|
61
|
+
</TooltipPrimitive.Positioner>
|
|
62
|
+
</TooltipPrimitive.Portal>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent };
|
|
@@ -11,9 +11,10 @@ type McpServerType = McpServerSpec["serverType"];
|
|
|
11
11
|
/**
|
|
12
12
|
* Whether an MCP server's transport is stdio while connected to Stigmer Cloud.
|
|
13
13
|
*
|
|
14
|
-
* stdio
|
|
15
|
-
*
|
|
16
|
-
* that combination so the
|
|
14
|
+
* stdio is local-runner-only: cloud-hosted sessions refuse stdio servers at
|
|
15
|
+
* execution create, and the cloud connect flow refuses to spawn them. This
|
|
16
|
+
* predicate identifies exactly that combination so the UI can explain the
|
|
17
|
+
* policy and its remediation (run the session on a local runner). HTTP
|
|
17
18
|
* servers and any transport in local mode return `false`.
|
|
18
19
|
*
|
|
19
20
|
* Pure and framework-free so it can be unit-tested without rendering.
|
|
@@ -34,15 +35,15 @@ export interface StdioSandboxNoticeProps {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
-
* **Stigmer Cloud**.
|
|
38
|
+
* Explains the local-runner-only policy for an **stdio** MCP server viewed
|
|
39
|
+
* from **Stigmer Cloud**.
|
|
39
40
|
*
|
|
40
|
-
* stdio servers
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* stdio servers spawn subprocesses on the machine that runs the agent, so
|
|
42
|
+
* they run only on local runners — Stigmer-managed cloud compute refuses
|
|
43
|
+
* them (at execution create, and in the connect flow). The server remains
|
|
44
|
+
* fully usable on sessions that execute on a local runner (desktop app,
|
|
45
|
+
* `stigmer server`), where tools are discovered automatically at session
|
|
46
|
+
* start. This notice states that up front so the refusal never surprises.
|
|
46
47
|
*
|
|
47
48
|
* Self-gating: reads the deployment mode from context and renders `null`
|
|
48
49
|
* unless the server is stdio and the backend is cloud. Callers render it
|
|
@@ -68,9 +69,10 @@ export function StdioSandboxNotice({
|
|
|
68
69
|
>
|
|
69
70
|
<SandboxIcon className="mt-0.5 size-4 shrink-0" />
|
|
70
71
|
<p className="text-xs leading-relaxed">
|
|
71
|
-
This stdio server runs
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
This stdio server runs only on local runners — it won't be
|
|
73
|
+
available to sessions on Stigmer-managed cloud compute. Run the
|
|
74
|
+
session on a local runner (desktop app or CLI) to use it, or choose
|
|
75
|
+
a remote (HTTP) server instead.
|
|
74
76
|
</p>
|
|
75
77
|
</div>
|
|
76
78
|
);
|
|
@@ -44,10 +44,11 @@ function withMode(mode: "cloud" | "local", children: ReactNode) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
describe("StdioSandboxNotice", () => {
|
|
47
|
-
it("renders the
|
|
47
|
+
it("renders the local-runner-only policy for stdio in cloud", () => {
|
|
48
48
|
render(withMode("cloud", <StdioSandboxNotice serverType={STDIO} />));
|
|
49
49
|
expect(screen.getByRole("status")).toBeTruthy();
|
|
50
|
-
expect(screen.getByText(/
|
|
50
|
+
expect(screen.getByText(/only on local runners/i)).toBeTruthy();
|
|
51
|
+
expect(screen.getByText(/remote \(HTTP\) server/i)).toBeTruthy();
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
it("does not suggest the CLI (avoids inaccurate guidance)", () => {
|
|
@@ -223,6 +223,13 @@ export function IdentityTransportStep({
|
|
|
223
223
|
onChange={() => updateData({ transportType: "stdio" })}
|
|
224
224
|
/>
|
|
225
225
|
</div>
|
|
226
|
+
{data.transportType === "stdio" && (
|
|
227
|
+
<p className="text-xs text-muted-foreground">
|
|
228
|
+
Stdio servers run only on local runners (desktop app or CLI).
|
|
229
|
+
Sessions on Stigmer-managed cloud compute require a remote (HTTP)
|
|
230
|
+
server.
|
|
231
|
+
</p>
|
|
232
|
+
)}
|
|
226
233
|
|
|
227
234
|
{/* HTTP fields */}
|
|
228
235
|
{data.transportType === "http" && (
|
|
@@ -62,41 +62,44 @@ export const MCP_SERVER_TEMPLATES: readonly ResourceTemplate<McpServerWizardData
|
|
|
62
62
|
},
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
|
-
id: "
|
|
66
|
-
name: "
|
|
65
|
+
id: "neon",
|
|
66
|
+
name: "Neon",
|
|
67
67
|
description:
|
|
68
|
-
"Connect to
|
|
68
|
+
"Connect to Neon's hosted MCP endpoint for serverless PostgreSQL — querying, schema inspection, and branch management.",
|
|
69
69
|
category: "integration",
|
|
70
|
-
tags: ["postgres", "postgresql", "database", "sql", "data"],
|
|
70
|
+
tags: ["neon", "postgres", "postgresql", "database", "sql", "data"],
|
|
71
71
|
data: {
|
|
72
|
-
name: "
|
|
72
|
+
name: "Neon",
|
|
73
73
|
description:
|
|
74
|
-
"
|
|
75
|
-
transportType: "
|
|
76
|
-
|
|
77
|
-
stdioArgs: "-y @modelcontextprotocol/server-postgres",
|
|
74
|
+
"Neon serverless PostgreSQL — querying, schema inspection, database provisioning, and branch management.",
|
|
75
|
+
transportType: "http",
|
|
76
|
+
httpUrl: "https://mcp.neon.tech/mcp",
|
|
78
77
|
env: [
|
|
79
78
|
{
|
|
80
|
-
key: "
|
|
79
|
+
key: "NEON_API_KEY",
|
|
81
80
|
description:
|
|
82
|
-
"
|
|
81
|
+
"Neon API key (generate at console.neon.tech/app/settings/api-keys).",
|
|
83
82
|
isSecret: true,
|
|
84
83
|
optional: false,
|
|
85
84
|
},
|
|
86
85
|
],
|
|
87
86
|
},
|
|
88
87
|
},
|
|
88
|
+
// Deliberately stdio: the canonical example of a tool that must run on
|
|
89
|
+
// the user's own machine. Stdio servers are local-runner-only — the
|
|
90
|
+
// wizard's transport step states this, and cloud-targeted sessions
|
|
91
|
+
// refuse them at execution create.
|
|
89
92
|
{
|
|
90
93
|
id: "filesystem",
|
|
91
|
-
name: "Filesystem",
|
|
94
|
+
name: "Filesystem (local runners)",
|
|
92
95
|
description:
|
|
93
|
-
"Provide read and write access to a local directory for file management and content operations.",
|
|
96
|
+
"Provide read and write access to a local directory for file management and content operations. Runs on local runners only.",
|
|
94
97
|
category: "general",
|
|
95
98
|
tags: ["filesystem", "files", "local", "directory"],
|
|
96
99
|
data: {
|
|
97
100
|
name: "Filesystem",
|
|
98
101
|
description:
|
|
99
|
-
"Local filesystem access for reading, writing, and managing files within a directory.",
|
|
102
|
+
"Local filesystem access for reading, writing, and managing files within a directory. Stdio transport — runs on local runners only.",
|
|
100
103
|
transportType: "stdio",
|
|
101
104
|
stdioCommand: "npx",
|
|
102
105
|
stdioArgs:
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { ArrowLeft } from "lucide-react";
|
|
5
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
6
|
+
import type { SettingsNavGroup } from "../settings/settings-nav.js";
|
|
7
|
+
import { SidebarChrome, SidebarSeparator, navRowClassName } from "./chrome.js";
|
|
8
|
+
import type { RenderSidebarLink } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Public API
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Props for {@link SettingsSidebar}. */
|
|
15
|
+
export interface SettingsSidebarProps {
|
|
16
|
+
/**
|
|
17
|
+
* Grouped settings navigation to render. The console passes
|
|
18
|
+
* `useSettingsNavGroups()` (permission-aware); deterministic hosts
|
|
19
|
+
* pass `SETTINGS_NAV_GROUPS` or a fixture subset.
|
|
20
|
+
*/
|
|
21
|
+
readonly groups: readonly SettingsNavGroup[];
|
|
22
|
+
/**
|
|
23
|
+
* Current pathname for active-row matching: a row is active when the
|
|
24
|
+
* path equals its `href` or sits under `href + "/"`. Omit for hosts
|
|
25
|
+
* with no location (deterministic embeds pass the depicted route).
|
|
26
|
+
*/
|
|
27
|
+
readonly activePath?: string | null;
|
|
28
|
+
/** Renders each interactive row. See {@link RenderSidebarLink}. */
|
|
29
|
+
readonly renderLink: RenderSidebarLink;
|
|
30
|
+
/**
|
|
31
|
+
* "Back to Sessions" target — the last workspace-zone location, so
|
|
32
|
+
* leaving settings restores where the user was. @default "/"
|
|
33
|
+
*/
|
|
34
|
+
readonly backHref?: string;
|
|
35
|
+
/** Footer content — typically the host app's `UserMenu` wrapper. */
|
|
36
|
+
readonly footer: ReactNode;
|
|
37
|
+
/** Reflected as `aria-expanded` on the collapse toggle. @default true */
|
|
38
|
+
readonly isOpen?: boolean;
|
|
39
|
+
/** Called when the collapse toggle is pressed. */
|
|
40
|
+
readonly onCollapse?: () => void;
|
|
41
|
+
/** Passed through to the embedded `OrgSwitcher`. */
|
|
42
|
+
readonly onOrgChanged?: (org: Organization) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The console's settings-zone (management) sidebar: org switcher, a
|
|
47
|
+
* "Back to Sessions" exit, and grouped settings navigation driven by
|
|
48
|
+
* the SDK's settings-nav model, with a user footer.
|
|
49
|
+
*
|
|
50
|
+
* Like {@link WorkspaceSidebar}, this is the exact component the web
|
|
51
|
+
* console, desktop app, and documentation tours all render; hosts vary
|
|
52
|
+
* only `renderLink`, `groups`, and the `footer` slot.
|
|
53
|
+
*
|
|
54
|
+
* Row identifiers passed to `renderLink` are the settings route
|
|
55
|
+
* basenames (`"api-keys"`, `"members"`, …) plus `"back-to-sessions"`,
|
|
56
|
+
* so consumers can address specific rows without parsing hrefs.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <SettingsSidebar
|
|
61
|
+
* groups={useSettingsNavGroups()}
|
|
62
|
+
* activePath={usePathname()}
|
|
63
|
+
* backHref={lastSessionZonePath ?? "/"}
|
|
64
|
+
* renderLink={({ href, children, ...rest }) => (
|
|
65
|
+
* <Link href={href} {...rest}>{children}</Link>
|
|
66
|
+
* )}
|
|
67
|
+
* footer={<UserMenu />}
|
|
68
|
+
* />
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function SettingsSidebar({
|
|
72
|
+
groups,
|
|
73
|
+
activePath = null,
|
|
74
|
+
renderLink,
|
|
75
|
+
backHref = "/",
|
|
76
|
+
footer,
|
|
77
|
+
isOpen = true,
|
|
78
|
+
onCollapse,
|
|
79
|
+
onOrgChanged,
|
|
80
|
+
}: SettingsSidebarProps) {
|
|
81
|
+
return (
|
|
82
|
+
<SidebarChrome
|
|
83
|
+
ariaLabel="Management navigation"
|
|
84
|
+
isOpen={isOpen}
|
|
85
|
+
onCollapse={onCollapse}
|
|
86
|
+
onOrgChanged={onOrgChanged}
|
|
87
|
+
footer={footer}
|
|
88
|
+
>
|
|
89
|
+
{/* Back to Sessions */}
|
|
90
|
+
<div className="flex-none px-3 py-1">
|
|
91
|
+
{renderLink({
|
|
92
|
+
id: "back-to-sessions",
|
|
93
|
+
href: backHref,
|
|
94
|
+
active: false,
|
|
95
|
+
className: navRowClassName(false, { muted: true }),
|
|
96
|
+
"aria-current": undefined,
|
|
97
|
+
children: (
|
|
98
|
+
<>
|
|
99
|
+
<ArrowLeft className="size-4 shrink-0" />
|
|
100
|
+
Back to Sessions
|
|
101
|
+
</>
|
|
102
|
+
),
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<SidebarSeparator />
|
|
107
|
+
|
|
108
|
+
{/* Settings nav links — grouped, permission-aware via `groups` */}
|
|
109
|
+
<div className="flex flex-col gap-4 px-3 py-1">
|
|
110
|
+
{groups.map((group) => (
|
|
111
|
+
<div key={group.label} className="flex flex-col gap-0.5">
|
|
112
|
+
<span className="text-sidebar-muted-foreground px-2 pb-1 text-[11px] font-medium tracking-wider uppercase">
|
|
113
|
+
{group.label}
|
|
114
|
+
</span>
|
|
115
|
+
{group.items.map((item) => {
|
|
116
|
+
const active =
|
|
117
|
+
activePath != null &&
|
|
118
|
+
(activePath === item.href ||
|
|
119
|
+
activePath.startsWith(`${item.href}/`));
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<SettingsNavRow
|
|
123
|
+
key={item.href}
|
|
124
|
+
href={item.href}
|
|
125
|
+
label={item.label}
|
|
126
|
+
icon={<item.icon className="size-4 shrink-0" />}
|
|
127
|
+
active={active}
|
|
128
|
+
renderLink={renderLink}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Spacer pushes the footer to the bottom (no scrollable middle). */}
|
|
137
|
+
<div className="flex-1" />
|
|
138
|
+
</SidebarChrome>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Internals
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
function SettingsNavRow({
|
|
147
|
+
href,
|
|
148
|
+
label,
|
|
149
|
+
icon,
|
|
150
|
+
active,
|
|
151
|
+
renderLink,
|
|
152
|
+
}: {
|
|
153
|
+
readonly href: string;
|
|
154
|
+
readonly label: string;
|
|
155
|
+
readonly icon: ReactNode;
|
|
156
|
+
readonly active: boolean;
|
|
157
|
+
readonly renderLink: RenderSidebarLink;
|
|
158
|
+
}) {
|
|
159
|
+
return renderLink({
|
|
160
|
+
// Route basename as the stable id: "/settings/api-keys" → "api-keys".
|
|
161
|
+
id: href.slice(href.lastIndexOf("/") + 1),
|
|
162
|
+
href,
|
|
163
|
+
active,
|
|
164
|
+
className: navRowClassName(active),
|
|
165
|
+
"aria-current": active ? "page" : undefined,
|
|
166
|
+
children: (
|
|
167
|
+
<>
|
|
168
|
+
{icon}
|
|
169
|
+
{label}
|
|
170
|
+
</>
|
|
171
|
+
),
|
|
172
|
+
});
|
|
173
|
+
}
|