From 6aedeaf1f661c4a74a3d1a1c0686ae1e84eff7f6 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Tue, 22 Sep 2026 15:12:32 -0700 Subject: [PATCH] improvement(ui): reuse collapsible cards for MCP tools --- .../settings/components/mcp/mcp.tsx | 107 +++++++++--------- .../collapsible-card.test.tsx | 32 ++++++ .../collapsible-card/collapsible-card.tsx | 13 ++- 3 files changed, 96 insertions(+), 56 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx index 488a0ef206c..3a04667501f 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx @@ -1,7 +1,16 @@ 'use client' import { useEffect, useRef, useState } from 'react' -import { Badge, Button, Chip, ChipConfirmModal, cn, Tooltip, toast } from '@sim/emcn' +import { + Badge, + Chip, + ChipConfirmModal, + CollapsibleCard, + cn, + OverflowText, + Tooltip, + toast, +} from '@sim/emcn' import { ArrowLeft, ChevronDown, Plus } from '@sim/emcn/icons' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' @@ -535,59 +544,53 @@ export function MCP() { const requiredParams = tool.inputSchema?.required || [] return ( -
- - + + } + badge={ + <> + {issues.length > 0 && ( + + + + + {getIssueBadgeLabel(issues[0].issue)} + + + + + Update in: {affectedWorkflows.join(', ')} + + + )} + {hasParams && ( + + )} + + } + > {isExpanded && hasParams && ( -
-

+ <> +

Parameters

@@ -631,9 +634,9 @@ export function MCP() { } )}
-
+ )} -
+ ) })} diff --git a/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx b/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx index a61dba4b2eb..12da6d82427 100644 --- a/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx +++ b/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx @@ -62,6 +62,38 @@ describe('CollapsibleCard', () => { } ) + it('prevents disabled header activation without disabling independent actions', () => { + const toggle = vi.fn() + const action = vi.fn() + mount( + + Refresh + + } + > + Content + + ) + const trigger = container!.querySelector('[role="button"]')! + expect(trigger.tabIndex).toBe(-1) + expect(trigger.getAttribute('aria-disabled')).toBe('true') + act(() => { + trigger.click() + trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) + trigger.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true })) + container!.querySelector('button')!.click() + }) + expect(toggle).not.toHaveBeenCalled() + expect(action).toHaveBeenCalledTimes(1) + expect(container!.textContent).not.toContain('Content') + }) + it('keeps enabled and disabled actions outside the collapse target', () => { const toggle = vi.fn() const add = vi.fn() diff --git a/packages/emcn/src/components/collapsible-card/collapsible-card.tsx b/packages/emcn/src/components/collapsible-card/collapsible-card.tsx index 9104661a7ea..8bb9c460891 100644 --- a/packages/emcn/src/components/collapsible-card/collapsible-card.tsx +++ b/packages/emcn/src/components/collapsible-card/collapsible-card.tsx @@ -15,6 +15,8 @@ export interface CollapsibleCardProps badge?: React.ReactNode /** Header actions, outside the collapse target and arranged with standard spacing. */ actions?: React.ReactNode + /** Prevent header activation and remove it from tab order; independent actions stay available. */ + disabled?: boolean collapsed: boolean onToggleCollapse: () => void /** Animate expansion using the shared Expandable height transition. */ @@ -42,6 +44,7 @@ export function CollapsibleCard({ badge, actions, collapsed, + disabled = false, onToggleCollapse, animated = false, contentProps, @@ -58,16 +61,18 @@ export function CollapsibleCard({ <>
{ - if (event.target !== event.currentTarget) return + if (disabled || event.target !== event.currentTarget) return handleKeyboardActivation(event, onToggleCollapse) }} >