From 4639dc3255b0ccb11c17cfd54d1ec98ffcd2a3ad Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:26:11 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9C=20fix:=20Forward=20Ref=20to=20`MCP?= =?UTF-8?q?SubMenu`=20and=20`ArtifactsSubMenu`=20(#8696)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToolsDropdown uses a menu library that passes refs to submenu items. Function components can't receive refs by default though, so we get "Function components cannot be given refs" warnings in the console. React.forwardRef() allows them to properly handle ref forwarding by wrapping the component and attaching the ref to the outer div element. --- .../Chat/Input/ArtifactsSubMenu.tsx | 247 +++++++++--------- .../src/components/Chat/Input/MCPSubMenu.tsx | 208 +++++++-------- 2 files changed, 232 insertions(+), 223 deletions(-) diff --git a/client/src/components/Chat/Input/ArtifactsSubMenu.tsx b/client/src/components/Chat/Input/ArtifactsSubMenu.tsx index 654c129fba..e27fa43c0e 100644 --- a/client/src/components/Chat/Input/ArtifactsSubMenu.tsx +++ b/client/src/components/Chat/Input/ArtifactsSubMenu.tsx @@ -15,133 +15,142 @@ interface ArtifactsSubMenuProps { handleCustomToggle: () => void; } -const ArtifactsSubMenu = ({ - isArtifactsPinned, - setIsArtifactsPinned, - artifactsMode, - handleArtifactsToggle, - handleShadcnToggle, - handleCustomToggle, - ...props -}: ArtifactsSubMenuProps) => { - const localize = useLocalize(); +const ArtifactsSubMenu = React.forwardRef( + ( + { + isArtifactsPinned, + setIsArtifactsPinned, + artifactsMode, + handleArtifactsToggle, + handleShadcnToggle, + handleCustomToggle, + ...props + }, + ref, + ) => { + const localize = useLocalize(); - const menuStore = Ariakit.useMenuStore({ - focusLoop: true, - showTimeout: 100, - placement: 'right', - }); + const menuStore = Ariakit.useMenuStore({ + focusLoop: true, + showTimeout: 100, + placement: 'right', + }); - const isEnabled = artifactsMode !== '' && artifactsMode !== undefined; - const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI; - const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM; + const isEnabled = artifactsMode !== '' && artifactsMode !== undefined; + const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI; + const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM; - return ( - - ) => { - e.stopPropagation(); - handleArtifactsToggle(); - }} - onMouseEnter={() => { - if (isEnabled) { - menuStore.show(); - } - }} - className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover" - /> - } - > -
- - {localize('com_ui_artifacts')} - {isEnabled && } -
- -
- - {isEnabled && ( - -
-
- {localize('com_ui_artifacts_options')} + return ( +
+ + ) => { + e.stopPropagation(); + handleArtifactsToggle(); + }} + onMouseEnter={() => { + if (isEnabled) { + menuStore.show(); + } + }} + className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover" + /> + } + > +
+ + {localize('com_ui_artifacts')} + {isEnabled && }
- - {/* Include shadcn/ui Option */} - { - event.preventDefault(); - event.stopPropagation(); - handleShadcnToggle(); - }} - disabled={isCustomEnabled} - className={cn( - 'mb-1 flex items-center justify-between rounded-lg px-2 py-2', - 'cursor-pointer text-text-primary outline-none transition-colors', - 'hover:bg-black/[0.075] dark:hover:bg-white/10', - 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', - isCustomEnabled && 'cursor-not-allowed opacity-50', - )} - > -
- - {localize('com_ui_include_shadcnui' as any)} -
-
- - {/* Custom Prompt Mode Option */} - { - event.preventDefault(); - event.stopPropagation(); - handleCustomToggle(); + + + + {isEnabled && ( + -
- - {localize('com_ui_custom_prompt_mode' as any)} +
+
+ {localize('com_ui_artifacts_options')} +
+ + {/* Include shadcn/ui Option */} + { + event.preventDefault(); + event.stopPropagation(); + handleShadcnToggle(); + }} + disabled={isCustomEnabled} + className={cn( + 'mb-1 flex items-center justify-between rounded-lg px-2 py-2', + 'cursor-pointer text-text-primary outline-none transition-colors', + 'hover:bg-black/[0.075] dark:hover:bg-white/10', + 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', + isCustomEnabled && 'cursor-not-allowed opacity-50', + )} + > +
+ + {localize('com_ui_include_shadcnui' as any)} +
+
+ + {/* Custom Prompt Mode Option */} + { + event.preventDefault(); + event.stopPropagation(); + handleCustomToggle(); + }} + className={cn( + 'flex items-center justify-between rounded-lg px-2 py-2', + 'cursor-pointer text-text-primary outline-none transition-colors', + 'hover:bg-black/[0.075] dark:hover:bg-white/10', + 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', + )} + > +
+ + {localize('com_ui_custom_prompt_mode' as any)} +
+
- -
-
- )} -
- ); -}; + + )} + +
+ ); + }, +); + +ArtifactsSubMenu.displayName = 'ArtifactsSubMenu'; export default React.memo(ArtifactsSubMenu); diff --git a/client/src/components/Chat/Input/MCPSubMenu.tsx b/client/src/components/Chat/Input/MCPSubMenu.tsx index 1d704f2a6d..3628b732a1 100644 --- a/client/src/components/Chat/Input/MCPSubMenu.tsx +++ b/client/src/components/Chat/Input/MCPSubMenu.tsx @@ -11,115 +11,115 @@ interface MCPSubMenuProps { placeholder?: string; } -const MCPSubMenu = ({ placeholder, ...props }: MCPSubMenuProps) => { - const { - configuredServers, - mcpValues, - isPinned, - setIsPinned, - placeholderText, - toggleServerSelection, - getServerStatusIconProps, - getConfigDialogProps, - } = useMCPServerManager(); +const MCPSubMenu = React.forwardRef( + ({ placeholder, ...props }, ref) => { + const { + configuredServers, + mcpValues, + isPinned, + setIsPinned, + placeholderText, + toggleServerSelection, + getServerStatusIconProps, + getConfigDialogProps, + } = useMCPServerManager(); - const menuStore = Ariakit.useMenuStore({ - focusLoop: true, - showTimeout: 100, - placement: 'right', - }); + const menuStore = Ariakit.useMenuStore({ + focusLoop: true, + showTimeout: 100, + placement: 'right', + }); - // Don't render if no MCP servers are configured - if (!configuredServers || configuredServers.length === 0) { - return null; - } + // Don't render if no MCP servers are configured + if (!configuredServers || configuredServers.length === 0) { + return null; + } - const configDialogProps = getConfigDialogProps(); + const configDialogProps = getConfigDialogProps(); - return ( - <> - - ) => { - e.stopPropagation(); - menuStore.toggle(); - }} - className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover" - /> - } - > -
- - {placeholder || placeholderText} - -
- -
- - {configuredServers.map((serverName) => { - const statusIconProps = getServerStatusIconProps(serverName); - const isSelected = mcpValues?.includes(serverName) ?? false; - - const statusIcon = statusIconProps && ; - - return ( - { - event.preventDefault(); - toggleServerSelection(serverName); + return ( +
+ + ) => { + e.stopPropagation(); + menuStore.toggle(); }} - className={cn( - 'flex items-center gap-2 rounded-lg px-2 py-1.5 text-text-primary hover:cursor-pointer', - 'scroll-m-1 outline-none transition-colors', - 'hover:bg-black/[0.075] dark:hover:bg-white/10', - 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', - 'w-full min-w-0 justify-between text-sm', - )} - > - + + + {configuredServers.map((serverName) => { + const statusIconProps = getServerStatusIconProps(serverName); + const isSelected = mcpValues?.includes(serverName) ?? false; + + const statusIcon = statusIconProps && ; + + return ( + { + event.preventDefault(); + toggleServerSelection(serverName); + }} + className={cn( + 'flex items-center gap-2 rounded-lg px-2 py-1.5 text-text-primary hover:cursor-pointer', + 'scroll-m-1 outline-none transition-colors', + 'hover:bg-black/[0.075] dark:hover:bg-white/10', + 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', + 'w-full min-w-0 justify-between text-sm', + )} > - - {serverName} - - {statusIcon &&
{statusIcon}
} -
- ); - })} -
-
- {configDialogProps && } - - ); -}; +
+ + {serverName} +
+ {statusIcon &&
{statusIcon}
} + + ); + })} + + + {configDialogProps && } +
+ ); + }, +); + +MCPSubMenu.displayName = 'MCPSubMenu'; export default React.memo(MCPSubMenu);