@mcp-b/react-components 0.5.4 → 0.5.6

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.
@@ -30,6 +30,8 @@ interface AgentChatProps<MessageT extends UIMessage = UIMessage> {
30
30
  emptyState?: React.ReactNode;
31
31
  /** Extra controls rendered in the composer toolbar. */
32
32
  composerTools?: React.ReactNode;
33
+ /** Extra rows rendered in the built-in plus menu after files. */
34
+ composerAddMenuItems?: React.ReactNode;
33
35
  /**
34
36
  * Transient status rendered between the transcript and the composer —
35
37
  * e.g. a "Reconnecting…" pill. `ThinkChat.Messages` fills this from the
@@ -86,6 +88,7 @@ declare function AgentChat<MessageT extends UIMessage = UIMessage>({
86
88
  disabled,
87
89
  emptyState,
88
90
  composerTools,
91
+ composerAddMenuItems,
89
92
  notice,
90
93
  onRegenerate,
91
94
  onApprovalResponse,
@@ -4,7 +4,7 @@ import { Activity, ActivityContent, ActivityPanel, ActivityTrigger } from "./Act
4
4
  import { Context, ContextContent, ContextContentBody, ContextContentHeader, ContextTrigger, getContextUsage, getContextUsedTokens } from "./Context.js";
5
5
  import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "./Conversation.js";
6
6
  import { Message, MessageAction, MessageActions, MessageAttachment, MessageContent, MessageResponse } from "./Message.js";
7
- import { PromptInput, PromptInputApproval, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputFooter, PromptInputPermissionSelector, PromptInputSubmit, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getPendingToolApprovals, usePromptInputAttachments } from "./PromptInput.js";
7
+ import { PromptInput, PromptInputActionAddAttachments, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputFooter, PromptInputPermissionSelector, PromptInputSubmit, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getPendingToolApprovals, usePromptInputAttachments } from "./PromptInput.js";
8
8
  import { Reasoning, ReasoningContent, ReasoningTrigger } from "./Reasoning.js";
9
9
  import * as React from "react";
10
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -18,6 +18,17 @@ function AgentChatAttachButton() {
18
18
  children: /* @__PURE__ */ jsx(PlusIcon, {})
19
19
  });
20
20
  }
21
+ function AgentChatAddMenu({ children }) {
22
+ if (!children) return /* @__PURE__ */ jsx(AgentChatAttachButton, {});
23
+ return /* @__PURE__ */ jsxs(PromptInputActionMenu, { children: [/* @__PURE__ */ jsx(PromptInputActionMenuTrigger, { "aria-label": "Add context" }), /* @__PURE__ */ jsxs(PromptInputActionMenuContent, {
24
+ side: "top",
25
+ align: "start",
26
+ sideOffset: 12,
27
+ collisionPadding: 8,
28
+ className: "agent-chat__add-menu",
29
+ children: [/* @__PURE__ */ jsx(PromptInputActionAddAttachments, {}), children]
30
+ })] });
31
+ }
21
32
  function isCompactedPart(message, part, partIndex) {
22
33
  const metadata = message.metadata;
23
34
  if (isTextUIPart(part)) return Array.isArray(metadata?.compactedTextParts) && metadata.compactedTextParts.includes(partIndex);
@@ -88,7 +99,7 @@ function renderDefaultPart(part) {
88
99
  * transport. Use it directly with plain AI SDK `useChat` or a read-only
89
100
  * transcript; if you have a Cloudflare Think agent, use `ThinkChat` instead.
90
101
  */
91
- function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerTools, notice, onRegenerate, onApprovalResponse, executionApprovals, contextWindow, renderPart, renderMessageAfter, ref }) {
102
+ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerTools, composerAddMenuItems, notice, onRegenerate, onApprovalResponse, executionApprovals, contextWindow, renderPart, renderMessageAfter, ref }) {
92
103
  const handleSubmit = React.useCallback((message) => {
93
104
  onSubmit?.(message);
94
105
  }, [onSubmit]);
@@ -194,7 +205,7 @@ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, pl
194
205
  disabled
195
206
  }) }),
196
207
  /* @__PURE__ */ jsxs(PromptInputFooter, { children: [/* @__PURE__ */ jsxs(PromptInputTools, { children: [
197
- /* @__PURE__ */ jsx(AgentChatAttachButton, {}),
208
+ /* @__PURE__ */ jsx(AgentChatAddMenu, { children: composerAddMenuItems }),
198
209
  approvalsWired ? /* @__PURE__ */ jsx(PromptInputPermissionSelector, {
199
210
  value: permissionMode,
200
211
  onValueChange: setPermissionMode
@@ -1,15 +1,17 @@
1
1
  import * as React from "react";
2
- import { Icon } from "@modelcontextprotocol/sdk/types.js";
3
2
 
4
3
  //#region src/components/McpServerPicker.d.ts
5
4
  type McpServerPickerStatus = "connecting" | "needs-auth" | "authenticating" | "ready" | "failed";
5
+ interface McpServerCardIcon {
6
+ src: string;
7
+ }
6
8
  interface McpServerCard {
7
9
  id: string;
8
10
  name: string;
9
11
  url: string;
10
12
  description?: string;
11
13
  category?: string;
12
- icon?: React.ReactNode | string | Icon;
14
+ icon?: string | McpServerCardIcon;
13
15
  status?: McpServerPickerStatus;
14
16
  statusLabel?: string;
15
17
  }
@@ -21,6 +23,7 @@ interface McpServerPickerProps {
21
23
  /** Offset from the plus trigger to the menu, useful for prompt composers. */
22
24
  sideOffset?: number;
23
25
  }
26
+ type McpServerPickerItemsProps = Pick<McpServerPickerProps, "servers" | "onConnect">;
24
27
  /**
25
28
  * Prompt plus-menu picker for choosing an MCP server and handing connection
26
29
  * work back to the host app.
@@ -30,5 +33,9 @@ declare function McpServerPicker({
30
33
  onConnect,
31
34
  sideOffset
32
35
  }: McpServerPickerProps): React.JSX.Element;
36
+ declare function McpServerPickerItems({
37
+ servers,
38
+ onConnect
39
+ }: McpServerPickerItemsProps): React.JSX.Element | React.JSX.Element[];
33
40
  //#endregion
34
- export { McpServerCard, McpServerPicker, McpServerPickerProps, McpServerPickerStatus };
41
+ export { McpServerCard, McpServerCardIcon, McpServerPicker, McpServerPickerItems, McpServerPickerItemsProps, McpServerPickerProps, McpServerPickerStatus };
@@ -9,8 +9,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
9
9
  * work back to the host app.
10
10
  */
11
11
  function McpServerPicker({ servers, onConnect, sideOffset = 80 }) {
12
- const groups = groupServers(servers);
13
- const showHeadings = groups.some((group) => group.category);
14
12
  return /* @__PURE__ */ jsx("div", {
15
13
  className: "mcp-server-picker",
16
14
  children: /* @__PURE__ */ jsxs(MenuRoot, { children: [/* @__PURE__ */ jsx(MenuTrigger, { render: /* @__PURE__ */ jsx(Button, {
@@ -28,30 +26,42 @@ function McpServerPicker({ servers, onConnect, sideOffset = 80 }) {
28
26
  collisionPadding: 8,
29
27
  "aria-label": "MCP servers",
30
28
  className: "mcp-server-picker__popup",
31
- children: servers.length === 0 ? /* @__PURE__ */ jsx(MenuItem, {
32
- disabled: true,
33
- className: "mcp-server-picker__empty",
34
- children: "No MCP servers available."
35
- }) : groups.map((group) => /* @__PURE__ */ jsxs(MenuGroup, {
36
- className: "mcp-server-picker__group",
37
- children: [showHeadings ? /* @__PURE__ */ jsx(MenuGroupLabel, { children: group.category || "Servers" }) : null, group.servers.map((server) => /* @__PURE__ */ jsx(McpServerPickerRow, {
38
- server,
39
- onConnect
40
- }, server.id))]
41
- }, group.category))
29
+ children: /* @__PURE__ */ jsx(McpServerPickerItems, {
30
+ servers,
31
+ onConnect
32
+ })
42
33
  })] })
43
34
  });
44
35
  }
36
+ function McpServerPickerItems({ servers, onConnect }) {
37
+ const groups = groupServers(servers);
38
+ const showHeadings = groups.some((group) => group.category);
39
+ if (servers.length === 0) return /* @__PURE__ */ jsx(MenuItem, {
40
+ disabled: true,
41
+ className: "mcp-server-picker__empty",
42
+ children: "No MCP servers available."
43
+ });
44
+ return groups.map((group) => /* @__PURE__ */ jsxs(MenuGroup, {
45
+ className: "mcp-server-picker__group",
46
+ children: [showHeadings ? /* @__PURE__ */ jsx(MenuGroupLabel, { children: group.category || "Servers" }) : null, group.servers.map((server) => /* @__PURE__ */ jsx(McpServerPickerRow, {
47
+ server,
48
+ onConnect
49
+ }, server.id))]
50
+ }, group.category));
51
+ }
45
52
  function McpServerPickerRow({ server, onConnect }) {
46
53
  const descriptionId = React.useId();
47
54
  const statusText = server.statusLabel ?? getStatusLabel(server.status);
48
55
  const busy = server.status === "connecting" || server.status === "authenticating";
56
+ const disabled = busy || server.status === "ready";
49
57
  return /* @__PURE__ */ jsxs(MenuItem, {
50
58
  className: "mcp-server-picker__row",
59
+ closeOnClick: false,
51
60
  "data-status": server.status,
61
+ disabled,
52
62
  "aria-busy": busy || void 0,
53
63
  "aria-describedby": server.description ? descriptionId : void 0,
54
- onClick: () => void onConnect(server),
64
+ onClick: disabled ? void 0 : () => void onConnect(server),
55
65
  children: [
56
66
  /* @__PURE__ */ jsx("span", {
57
67
  className: "mcp-server-picker__icon",
@@ -93,9 +103,8 @@ function getStatusLabel(status) {
93
103
  return "";
94
104
  }
95
105
  function renderIcon(icon, name) {
96
- if (React.isValidElement(icon)) return icon;
97
106
  if (typeof icon === "string") return /* @__PURE__ */ jsx(IconImage, { src: icon });
98
- if (isMcpIcon(icon)) return /* @__PURE__ */ jsx(IconImage, { src: icon.src });
107
+ if (icon) return /* @__PURE__ */ jsx(IconImage, { src: icon.src });
99
108
  return /* @__PURE__ */ jsx("span", {
100
109
  className: "mcp-server-picker__fallback-icon",
101
110
  children: name.slice(0, 1)
@@ -110,9 +119,6 @@ function IconImage({ src }) {
110
119
  referrerPolicy: "no-referrer"
111
120
  });
112
121
  }
113
- function isMcpIcon(icon) {
114
- return Boolean(icon && typeof icon === "object" && "src" in icon && typeof icon.src === "string");
115
- }
116
122
  function groupServers(servers) {
117
123
  const groups = [];
118
124
  for (const server of servers) {
@@ -130,4 +136,4 @@ function groupServers(servers) {
130
136
  return groups;
131
137
  }
132
138
  //#endregion
133
- export { McpServerPicker };
139
+ export { McpServerPicker, McpServerPickerItems };
@@ -1,6 +1,6 @@
1
1
  import { A as SelectTriggerProps, M as SelectValueProps, S as SelectProps, f as SelectOption, j as SelectValue, r as SelectContentProps, s as SelectItemProps, t as Select } from "../Select-CRsKCJ9x.js";
2
2
  import { c as ComboboxGroup, d as ComboboxLabel, f as ComboboxList, g as ComboboxTrigger, h as ComboboxSeparator, l as ComboboxInput, m as ComboboxPositioner, p as ComboboxPopup, s as ComboboxEmpty, t as Combobox, u as ComboboxItem } from "../Combobox-D9anamNr.js";
3
- import { h as MenuItemProps, w as MenuProps, y as MenuPopupProps } from "../Menu-B5vDqZe_.js";
3
+ import { h as MenuItemProps, l as MenuContentProps, w as MenuProps } from "../Menu-B5vDqZe_.js";
4
4
  import { c as TabsList, n as TabPanel, t as Tab } from "../Tabs-D9EAq09D.js";
5
5
  import { r as ButtonProps } from "../Button-Ct5I6aTu.js";
6
6
  import * as React from "react";
@@ -261,7 +261,7 @@ declare function PromptInputActionMenuTrigger({
261
261
  children,
262
262
  ...props
263
263
  }: PromptInputActionMenuTriggerProps): React.JSX.Element;
264
- type PromptInputActionMenuContentProps = MenuPopupProps;
264
+ type PromptInputActionMenuContentProps = MenuContentProps;
265
265
  declare function PromptInputActionMenuContent({
266
266
  className,
267
267
  children,
@@ -4,7 +4,7 @@ import { Button } from "./Button.js";
4
4
  import { Tooltip, TooltipPopup, TooltipPortal, TooltipPositioner, TooltipTrigger } from "./Tooltip.js";
5
5
  import { t as AttachmentPreview } from "../attachment-preview-ChtC3HiU.js";
6
6
  import { Combobox, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxList, ComboboxSeparator } from "./Combobox.js";
7
- import { MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuRoot, MenuTrigger } from "./Menu.js";
7
+ import { MenuContent, MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuRoot, MenuTrigger } from "./Menu.js";
8
8
  import { Select, SelectContent, SelectItem, SelectPortal, SelectPositioner, SelectTrigger, SelectValue } from "./Select.js";
9
9
  import { Tab, TabPanel, TabsList } from "./Tabs.js";
10
10
  import * as React from "react";
@@ -538,11 +538,11 @@ function PromptInputActionMenuTrigger({ className, children, ...props }) {
538
538
  }) });
539
539
  }
540
540
  function PromptInputActionMenuContent({ className, children, ...props }) {
541
- return /* @__PURE__ */ jsx(MenuPortal, { children: /* @__PURE__ */ jsx(MenuPositioner, { children: /* @__PURE__ */ jsx(MenuPopup, {
541
+ return /* @__PURE__ */ jsx(MenuContent, {
542
542
  className: cx("prompt-input__action-menu", className),
543
543
  ...props,
544
544
  children
545
- }) }) });
545
+ });
546
546
  }
547
547
  function PromptInputActionMenuItem({ className, ...props }) {
548
548
  return /* @__PURE__ */ jsx(MenuItem, {
@@ -31,6 +31,8 @@ interface ThinkChatMessagesProps extends ThinkChatActivityRendererOptions {
31
31
  emptyState?: React.ReactNode;
32
32
  /** Extra controls rendered inside the composer, next to the send button. */
33
33
  composerTools?: React.ReactNode;
34
+ /** Extra rows rendered in the built-in plus menu after files. */
35
+ composerAddMenuItems?: AgentChatProps["composerAddMenuItems"];
34
36
  /** Composer placeholder text. */
35
37
  placeholder?: string;
36
38
  /** Rendered after each message (e.g. per-message actions). */
@@ -43,6 +43,14 @@
43
43
  margin-inline: auto;
44
44
  }
45
45
 
46
+ .agent-chat__add-menu {
47
+ inline-size: min(32rem, calc(100dvw - 1rem));
48
+ max-block-size: min(28rem, calc(100dvh - 2rem));
49
+ overflow-y: auto;
50
+ overscroll-behavior: contain;
51
+ scrollbar-gutter: stable;
52
+ }
53
+
46
54
  /* Transient status pill between transcript and composer (recovery, errors). */
47
55
  .agent-chat__notice {
48
56
  display: flex;
@@ -108,6 +108,12 @@
108
108
  animation: mcp-server-picker-spin 900ms linear infinite;
109
109
  }
110
110
 
111
+ @media (prefers-reduced-motion: reduce) {
112
+ .mcp-server-picker__spinner {
113
+ animation: none;
114
+ }
115
+ }
116
+
111
117
  .mcp-server-picker__empty {
112
118
  padding-block: 0.5rem;
113
119
  padding-inline: 0.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/react-components",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
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": {
@@ -49,14 +49,13 @@
49
49
  "anser": "^2.3.5",
50
50
  "shiki": "^3.23.0",
51
51
  "streamdown": "^2.5.0",
52
- "@mcp-b/design-tokens": "0.5.4"
52
+ "@mcp-b/design-tokens": "0.5.6"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@ai-sdk/react": "^3.0.194",
56
56
  "@cloudflare/shell": "^0.4.1",
57
57
  "@cloudflare/think": "^0.12.1",
58
58
  "@cloudflare/workers-types": "^4.20260630.1",
59
- "@modelcontextprotocol/sdk": "^1.29.0",
60
59
  "@storybook/addon-a11y": "^10.4.6",
61
60
  "@storybook/addon-docs": "^10.4.6",
62
61
  "@storybook/addon-mcp": "^0.6.0",
@@ -84,7 +83,6 @@
84
83
  "@ai-sdk/react": "^3.0.194",
85
84
  "@cloudflare/shell": "^0.4.1",
86
85
  "@cloudflare/think": "^0.12.1",
87
- "@modelcontextprotocol/sdk": "^1.29.0",
88
86
  "agents": "^0.17.3",
89
87
  "ai": "^6.0.182",
90
88
  "react": "^19.0.0",
@@ -100,9 +98,6 @@
100
98
  "@cloudflare/think": {
101
99
  "optional": true
102
100
  },
103
- "@modelcontextprotocol/sdk": {
104
- "optional": true
105
- },
106
101
  "agents": {
107
102
  "optional": true
108
103
  },