@mcp-b/react-components 0.31.0 → 0.31.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.
@@ -67,6 +67,8 @@ interface ThinkFullAppProps<AgentT extends ThinkFullAppAgent<State>, State = unk
67
67
  threadStatusSummary?: ThinkThreadStatusSummary;
68
68
  /** Whether narrow layouts render the built-in chat/workbench switch. Defaults to true. */
69
69
  showMobileViewSwitch?: boolean;
70
+ /** Whether desktop layouts render the built-in workbench switch. Defaults to true. */
71
+ showWorkbenchViewSwitch?: boolean;
70
72
  /** Hide the thread app bar when a narrow workbench supplies its own navigation. */
71
73
  hideAppBarInWorkbench?: boolean;
72
74
  /** Whole native execution selected by the application route. */
@@ -112,6 +114,12 @@ interface ThinkFullAppProps<AgentT extends ThinkFullAppAgent<State>, State = unk
112
114
  defaultWorkbenchView?: ThinkFullAppWorkbenchView;
113
115
  /** Called when the shell toolbar asks to switch workbench lanes. */
114
116
  onWorkbenchViewChange?: (view: ThinkFullAppWorkbenchView) => void;
117
+ /** Whether the desktop workbench panel is open. Omit for internal state. */
118
+ workbenchOpen?: boolean;
119
+ /** Initial desktop workbench state when `workbenchOpen` is uncontrolled. */
120
+ defaultWorkbenchOpen?: boolean;
121
+ /** Called when toolbar or resize input opens or closes the workbench. */
122
+ onWorkbenchOpenChange?: (open: boolean) => void;
115
123
  }
116
124
  /** Combines ThinkChat with an optional resizable workbench shell. */
117
125
  declare function ThinkFullApp<AgentT extends ThinkFullAppAgent<State>, State = unknown, MessageT extends UIMessage = UIMessage>({
@@ -121,6 +129,7 @@ declare function ThinkFullApp<AgentT extends ThinkFullAppAgent<State>, State = u
121
129
  className,
122
130
  conversation,
123
131
  defaultMobileView,
132
+ defaultWorkbenchOpen,
124
133
  defaultWorkbenchView,
125
134
  defaultThreadSidebarOpen,
126
135
  hideAppBarInWorkbench,
@@ -128,6 +137,7 @@ declare function ThinkFullApp<AgentT extends ThinkFullAppAgent<State>, State = u
128
137
  mobileView,
129
138
  onMobileViewChange,
130
139
  onThreadSidebarOpenChange,
140
+ onWorkbenchOpenChange,
131
141
  onWorkbenchViewChange,
132
142
  plugins,
133
143
  ref,
@@ -135,11 +145,13 @@ declare function ThinkFullApp<AgentT extends ThinkFullAppAgent<State>, State = u
135
145
  renderExecutionRunLink,
136
146
  selectedExecutionRun,
137
147
  showMobileViewSwitch,
148
+ showWorkbenchViewSwitch,
138
149
  sourceKey,
139
150
  style,
140
151
  threadSidebar,
141
152
  threadSidebarOpen,
142
153
  threadStatusSummary,
154
+ workbenchOpen,
143
155
  workbenchPanels,
144
156
  workbenchView,
145
157
  workspace,
@@ -21,6 +21,7 @@ const workbenchPanelDefaultWidth = 340;
21
21
  const workbenchPanelMinWidth = 300;
22
22
  const workbenchPanelMaxWidth = 600;
23
23
  const workbenchPanelKeyboardStep = 24;
24
+ const workbenchPanelCollapseThreshold = 240;
24
25
  function WorkspacePanel({ config }) {
25
26
  const selectedPath = config.navigation.current.destination.kind === "file" ? config.navigation.current.destination.path : null;
26
27
  return /* @__PURE__ */ jsx(WorkspaceExplorer, {
@@ -81,15 +82,17 @@ function ThinkFullAppThreadChat({ agent, chat, messages, renderAgentToolRunLink,
81
82
  });
82
83
  }
83
84
  /** Combines ThinkChat with an optional resizable workbench shell. */
84
- function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, conversation, defaultMobileView, defaultWorkbenchView, defaultThreadSidebarOpen, hideAppBarInWorkbench = false, messages, mobileView, onMobileViewChange, onThreadSidebarOpenChange, onWorkbenchViewChange, plugins, ref, renderAgentToolRunLink, renderExecutionRunLink, selectedExecutionRun = null, showMobileViewSwitch = true, sourceKey, style, threadSidebar, threadSidebarOpen, threadStatusSummary, workbenchPanels, workbenchView, workspace, ...props }) {
85
+ function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, conversation, defaultMobileView, defaultWorkbenchOpen, defaultWorkbenchView, defaultThreadSidebarOpen, hideAppBarInWorkbench = false, messages, mobileView, onMobileViewChange, onThreadSidebarOpenChange, onWorkbenchOpenChange, onWorkbenchViewChange, plugins, ref, renderAgentToolRunLink, renderExecutionRunLink, selectedExecutionRun = null, showMobileViewSwitch = true, showWorkbenchViewSwitch = true, sourceKey, style, threadSidebar, threadSidebarOpen, threadStatusSummary, workbenchOpen, workbenchPanels, workbenchView, workspace, ...props }) {
85
86
  const agent = conversation?.agent;
86
87
  const appRef = React.useRef(null);
87
88
  const workbenchPanelRef = React.useRef(null);
88
89
  const pendingPanelFocusRef = React.useRef(null);
89
90
  const [innerMobileView, setInnerMobileView] = React.useState(defaultMobileView ?? "chat");
90
91
  const [innerWorkbenchView, setInnerWorkbenchView] = React.useState(defaultWorkbenchView ?? "files");
92
+ const [innerWorkbenchOpen, setInnerWorkbenchOpen] = React.useState(defaultWorkbenchOpen ?? true);
91
93
  const requestedMobileView = mobileView ?? innerMobileView;
92
94
  const requestedWorkbenchView = workbenchView ?? innerWorkbenchView;
95
+ const requestedWorkbenchOpen = workbenchOpen ?? innerWorkbenchOpen;
93
96
  const setWorkbenchView = React.useCallback((next) => {
94
97
  if (workbenchView === void 0) setInnerWorkbenchView(next);
95
98
  onWorkbenchViewChange?.(next);
@@ -98,6 +101,10 @@ function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, co
98
101
  if (mobileView === void 0) setInnerMobileView(next);
99
102
  onMobileViewChange?.(next);
100
103
  }, [mobileView, onMobileViewChange]);
104
+ const setWorkbenchOpen = React.useCallback((next) => {
105
+ if (workbenchOpen === void 0) setInnerWorkbenchOpen(next);
106
+ onWorkbenchOpenChange?.(next);
107
+ }, [onWorkbenchOpenChange, workbenchOpen]);
101
108
  const updateAppView = React.useCallback((update) => {
102
109
  React.startTransition(update);
103
110
  }, []);
@@ -135,13 +142,15 @@ function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, co
135
142
  render: () => /* @__PURE__ */ jsx(PluginsPanel, { config: plugins })
136
143
  });
137
144
  if (workbenchPanels) availableWorkbenchPanels.push(...workbenchPanels);
138
- const activePanel = availableWorkbenchPanels.find((panel) => panel.id === requestedWorkbenchView) ?? availableWorkbenchPanels[0];
145
+ const selectedPanel = availableWorkbenchPanels.find((panel) => panel.id === requestedWorkbenchView) ?? availableWorkbenchPanels[0];
146
+ const activePanel = requestedWorkbenchOpen ? selectedPanel : void 0;
139
147
  const activeWorkbenchView = activePanel?.id;
140
148
  const hasWorkbench = activePanel !== void 0;
149
+ const hasAvailableWorkbench = selectedPanel !== void 0;
141
150
  const activeMobileView = hasWorkbench ? requestedMobileView : "chat";
142
- const showWorkbenchViewSwitch = availableWorkbenchPanels.length > 1;
151
+ const shouldShowWorkbenchViewSwitch = showWorkbenchViewSwitch && availableWorkbenchPanels.length > 0;
143
152
  const workbenchViews = availableWorkbenchPanels;
144
- const shouldRenderInlineAppBar = !threadSidebar && (Boolean(appBarActions || appBarLeading || appBarTitle || threadSidebar) || hasWorkbench);
153
+ const shouldRenderInlineAppBar = !threadSidebar && (Boolean(appBarActions || appBarLeading || appBarTitle || threadSidebar) || hasAvailableWorkbench);
145
154
  const hasAppBarRow = shouldRenderInlineAppBar;
146
155
  React.useLayoutEffect(() => {
147
156
  const pendingFocus = pendingPanelFocusRef.current;
@@ -189,19 +198,27 @@ function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, co
189
198
  }) : null] }) : null,
190
199
  /* @__PURE__ */ jsxs(AppTopBar.Trailing, { children: [
191
200
  appBarActions,
192
- showWorkbenchViewSwitch && activeWorkbenchView ? /* @__PURE__ */ jsx(ThinkFullAppViewSwitch, {
201
+ shouldShowWorkbenchViewSwitch && selectedPanel ? /* @__PURE__ */ jsx(ThinkFullAppViewSwitch, {
193
202
  className: "think-full-app__workbench-view-switch",
194
203
  views: workbenchViews,
195
204
  value: activeWorkbenchView,
205
+ allowEmpty: true,
196
206
  onValueChange: (next) => {
197
- if (next) updateWorkbenchPanel(next);
207
+ updateAppView(() => {
208
+ if (!next) {
209
+ setWorkbenchOpen(false);
210
+ return;
211
+ }
212
+ updateWorkbenchPanel(next);
213
+ setWorkbenchOpen(true);
214
+ });
198
215
  },
199
216
  "aria-label": "Workbench views"
200
217
  }) : null,
201
- activePanel && showMobileViewSwitch ? /* @__PURE__ */ jsx(ThinkFullAppViewSwitch, {
218
+ selectedPanel && showMobileViewSwitch ? /* @__PURE__ */ jsx(ThinkFullAppViewSwitch, {
202
219
  className: "think-full-app__mobile-view-switch",
203
220
  views: workbenchViews,
204
- value: activeMobileView === "workbench" ? activePanel.id : void 0,
221
+ value: activeMobileView === "workbench" ? activeWorkbenchView : void 0,
205
222
  allowEmpty: true,
206
223
  onValueChange: (next) => {
207
224
  updateAppView(() => {
@@ -227,6 +244,8 @@ function ThinkFullApp({ appBarActions, appBarLeading, appBarTitle, className, co
227
244
  minSize: workbenchPanelMinWidth,
228
245
  maxSize: workbenchPanelMaxWidth,
229
246
  keyboardStep: workbenchPanelKeyboardStep,
247
+ collapseThreshold: workbenchPanelCollapseThreshold,
248
+ onCollapse: () => setWorkbenchOpen(false),
230
249
  "data-has-thread-sidebar": threadSidebar ? "true" : "false",
231
250
  "data-has-workbench": hasWorkbench ? "true" : "false",
232
251
  "data-has-workbench-topbar": hasAppBarRow ? "true" : "false",
@@ -41,7 +41,11 @@ interface ThinkThreadPickerProps extends Omit<React.HTMLAttributes<HTMLElement>,
41
41
  * inferred from the consuming application's registered route tree.
42
42
  */
43
43
  renderThreadLink: (thread: ThinkThreadItem) => React.ReactElement;
44
- /** Optional parent-agent create action, usually `parent.stub.createThread()`. */
44
+ /**
45
+ * Optional create action. An app whose threads are minted by their first
46
+ * message should navigate to its new-thread screen here rather than create
47
+ * one: a thread created on click is an empty thread until someone writes in it.
48
+ */
45
49
  onCreate?: () => void | Promise<void>;
46
50
  /**
47
51
  * Notified on unmodified primary activation. The link still navigates — the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/react-components",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "MCP-B React components built on Base UI and shared design tokens, including Cloudflare Think chat primitives.",
5
5
  "homepage": "https://design-system.sigvelo.com",
6
6
  "bugs": {
@@ -58,7 +58,7 @@
58
58
  "streamdown": "^2.5.0",
59
59
  "unist-util-visit": "^5.1.0",
60
60
  "yet-another-react-lightbox": "^3.32.1",
61
- "@mcp-b/design-tokens": "0.31.0"
61
+ "@mcp-b/design-tokens": "0.31.1"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@ai-sdk/react": "^3.0.230",