Skip to content

Commit 075e236

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
refactor(ui): share static API and MCP parameter cards
1 parent b16927a commit 075e236

7 files changed

Lines changed: 200 additions & 137 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/workflow-mcp-servers.tsx‎

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ChipSelect,
1919
Code,
2020
type ComboboxOption,
21+
FieldCard,
2122
Label,
2223
useCopyToClipboard,
2324
} from '@sim/emcn'
@@ -713,36 +714,29 @@ function ServerDetailView({
713714
return hasParams ? (
714715
<div className='flex flex-col gap-2'>
715716
{Object.entries(properties).map(([name, prop]) => (
716-
<div
717+
<FieldCard
717718
key={name}
718-
className='overflow-hidden rounded-sm border border-[var(--border-1)]'
719+
title={name}
720+
badge={
721+
<Badge variant='type' size='sm'>
722+
{prop.type || 'any'}
723+
</Badge>
724+
}
719725
>
720-
<div className='flex items-center justify-between bg-[var(--surface-4)] px-2.5 py-[5px]'>
721-
<div className='flex min-w-0 flex-1 items-center gap-2'>
722-
<span className='block truncate text-[var(--text-tertiary)] text-base'>
723-
{name}
724-
</span>
725-
<Badge variant='type' size='sm'>
726-
{prop.type || 'any'}
727-
</Badge>
728-
</div>
729-
</div>
730-
<div className='rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5'>
731-
<div className='flex flex-col gap-1.5'>
732-
<Label>Description</Label>
733-
<ChipInput
734-
value={editingParameterDescriptions[name] || ''}
735-
onChange={(e) =>
736-
setEditingParameterDescriptions((prev) => ({
737-
...prev,
738-
[name]: e.target.value,
739-
}))
740-
}
741-
placeholder={`Enter description for ${name}`}
742-
/>
743-
</div>
726+
<div className='flex flex-col gap-1.5'>
727+
<Label>Description</Label>
728+
<ChipInput
729+
value={editingParameterDescriptions[name] || ''}
730+
onChange={(e) =>
731+
setEditingParameterDescriptions((prev) => ({
732+
...prev,
733+
[name]: e.target.value,
734+
}))
735+
}
736+
placeholder={`Enter description for ${name}`}
737+
/>
744738
</div>
745-
</div>
739+
</FieldCard>
746740
))}
747741
</div>
748742
) : (

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx‎

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ChipModalField,
1414
ChipModalFooter,
1515
ChipModalHeader,
16+
FieldCard,
1617
Label,
1718
} from '@sim/emcn'
1819
import { getErrorMessage } from '@sim/utils/errors'
@@ -237,31 +238,24 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro
237238
<ChipModalField type='custom' title={`Parameters (${inputFormat.length})`}>
238239
<div className='flex flex-col gap-2'>
239240
{inputFormat.map((field) => (
240-
<div
241+
<FieldCard
241242
key={field.name}
242-
className='overflow-hidden rounded-sm border border-[var(--border-1)]'
243+
title={field.name}
244+
badge={
245+
<Badge variant='type' size='sm'>
246+
{field.type || 'string'}
247+
</Badge>
248+
}
243249
>
244-
<div className='flex items-center justify-between bg-[var(--surface-4)] px-2.5 py-[5px]'>
245-
<div className='flex min-w-0 flex-1 items-center gap-2'>
246-
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
247-
{field.name}
248-
</span>
249-
<Badge variant='type' size='sm'>
250-
{field.type || 'string'}
251-
</Badge>
252-
</div>
250+
<div className='flex flex-col gap-1.5'>
251+
<Label className='text-small'>Description</Label>
252+
<ChipInput
253+
value={paramDescriptions[field.name] || ''}
254+
onChange={(e) => handleParamDescriptionChange(field.name, e.target.value)}
255+
placeholder={`Enter description for ${field.name}`}
256+
/>
253257
</div>
254-
<div className='rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5'>
255-
<div className='flex flex-col gap-1.5'>
256-
<Label className='text-small'>Description</Label>
257-
<ChipInput
258-
value={paramDescriptions[field.name] || ''}
259-
onChange={(e) => handleParamDescriptionChange(field.name, e.target.value)}
260-
placeholder={`Enter description for ${field.name}`}
261-
/>
262-
</div>
263-
</div>
264-
</div>
258+
</FieldCard>
265259
))}
266260
</div>
267261
</ChipModalField>

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx‎

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ChipInput,
99
ChipModalField,
1010
type ComboboxOption,
11+
FieldCard,
1112
Skeleton,
1213
} from '@sim/emcn'
1314
import { createLogger } from '@sim/logger'
@@ -578,38 +579,31 @@ export function McpDeploy({
578579
</p>
579580
<div className='flex flex-col gap-2'>
580581
{inputFormat.map((field) => (
581-
<div
582+
<FieldCard
582583
key={field.name}
583-
className='overflow-hidden rounded-sm border border-[var(--border-1)]'
584+
title={field.name}
585+
badge={
586+
<Badge variant='type' size='sm'>
587+
{field.type}
588+
</Badge>
589+
}
584590
>
585-
<div className='flex items-center justify-between bg-[var(--surface-4)] px-2.5 py-[5px]'>
586-
<div className='flex min-w-0 flex-1 items-center gap-2'>
587-
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
588-
{field.name}
589-
</span>
590-
<Badge variant='type' size='sm'>
591-
{field.type}
592-
</Badge>
593-
</div>
594-
</div>
595-
<div className='rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5'>
596-
<ChipModalField
597-
type='input'
598-
title='Description'
599-
flush
600-
value={
601-
parameterDescriptions[field.name] ?? startBlockDescriptions[field.name] ?? ''
602-
}
603-
onChange={(value) =>
604-
setParameterDescriptions((prev) => ({
605-
...prev,
606-
[field.name]: value,
607-
}))
608-
}
609-
placeholder={startBlockDescriptions[field.name] || `Describe ${field.name}`}
610-
/>
611-
</div>
612-
</div>
591+
<ChipModalField
592+
type='input'
593+
title='Description'
594+
flush
595+
value={
596+
parameterDescriptions[field.name] ?? startBlockDescriptions[field.name] ?? ''
597+
}
598+
onChange={(value) =>
599+
setParameterDescriptions((prev) => ({
600+
...prev,
601+
[field.name]: value,
602+
}))
603+
}
604+
placeholder={startBlockDescriptions[field.name] || `Describe ${field.name}`}
605+
/>
606+
</FieldCard>
613607
))}
614608
</div>
615609
</ChipModalField>

‎packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @vitest-environment jsdom */
22
import { act, type ReactNode, useState } from 'react'
3-
import { CollapsibleCard } from '@sim/emcn'
3+
import { CollapsibleCard, FieldCard } from '@sim/emcn'
44
import { createRoot, type Root } from 'react-dom/client'
55
import { afterEach, describe, expect, it, vi } from 'vitest'
66

@@ -103,3 +103,19 @@ describe('CollapsibleCard', () => {
103103
expect(parentClick).not.toHaveBeenCalled()
104104
})
105105
})
106+
107+
it('keeps static FieldCard content visible without a collapse target', () => {
108+
mount(
109+
<FieldCard title='query' badge={<span>string</span>} data-field='query'>
110+
<input aria-label='Description' defaultValue='Search terms' />
111+
</FieldCard>
112+
)
113+
const card = container!.querySelector('[data-field="query"]')!
114+
const input = card.querySelector('input')!
115+
expect(card.textContent).toContain('query')
116+
expect(card.textContent).toContain('string')
117+
expect(card.querySelector('[role="button"], button, [aria-expanded]')).toBeNull()
118+
act(() => card.querySelector<HTMLElement>('[data-overflow-text]')!.click())
119+
expect(card.querySelector('input')).toBe(input)
120+
expect(input.value).toBe('Search terms')
121+
})

‎packages/emcn/src/components/collapsible-card/collapsible-card.tsx‎

Lines changed: 47 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type * as React from 'react'
44
import { cn } from '../../lib/cn'
55
import { handleKeyboardActivation } from '../../lib/keyboard'
66
import { Expandable, ExpandableContent } from '../expandable/expandable'
7+
import { FieldCardContent, FieldCardFrame } from '../field-card/field-card'
78
import { OverflowText, overflowTextClipClass } from '../overflow-text/overflow-text'
89

910
export interface CollapsibleCardProps
@@ -28,6 +29,7 @@ export interface CollapsibleCardProps
2829
* A collapsible field card: a `--surface-4` header (click / keyboard to toggle)
2930
* with a fade-clipped title + optional badge, over a `--surface-2` body. Shared by
3031
* the workflow input-mapping rows and the enrichment output-column config.
32+
* Its frame and body are also used by the always-open FieldCard.
3133
*
3234
* @example
3335
* <CollapsibleCard title='Condition' collapsed={collapsed} onToggleCollapse={toggle}
@@ -47,74 +49,62 @@ export function CollapsibleCard({
4749
className,
4850
...props
4951
}: CollapsibleCardProps) {
50-
const content = (
51-
<div
52-
{...contentProps}
53-
className={cn(
54-
'flex flex-col gap-2 rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5',
55-
contentProps?.className
56-
)}
57-
>
58-
{children}
59-
</div>
60-
)
52+
const content = <FieldCardContent {...contentProps}>{children}</FieldCardContent>
6153
return (
62-
<div
54+
<FieldCardFrame
6355
{...props}
64-
className={cn(
65-
'rounded-sm border border-[var(--border-1)]',
66-
collapsed ? 'overflow-hidden' : 'overflow-visible',
67-
className
68-
)}
69-
>
70-
<div className='flex items-center justify-between rounded-t-[4px] bg-[var(--surface-4)]'>
71-
<div
72-
role='button'
73-
tabIndex={0}
74-
aria-expanded={!collapsed}
75-
aria-controls={contentProps?.id}
76-
className={cn(
77-
'flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2.5 py-[5px]',
78-
actions && 'pr-2'
79-
)}
80-
onClick={onToggleCollapse}
81-
onKeyDown={(event) => {
82-
if (event.target !== event.currentTarget) return
83-
handleKeyboardActivation(event, onToggleCollapse)
84-
}}
85-
>
86-
{typeof title === 'string' || typeof title === 'number' ? (
87-
<OverflowText
88-
label={String(title)}
89-
className='flex-1 text-[var(--text-tertiary)] text-sm'
90-
focusTarget='nearest-interactive'
91-
/>
92-
) : (
93-
<span
94-
className={cn(overflowTextClipClass, 'flex-1 text-[var(--text-tertiary)] text-sm')}
95-
>
96-
{title}
97-
</span>
98-
)}
99-
{badge}
100-
</div>
101-
{actions && (
56+
className={cn(collapsed ? 'overflow-hidden' : 'overflow-visible', className)}
57+
header={
58+
<>
10259
<div
103-
role='presentation'
104-
className='flex shrink-0 items-center gap-2 py-[5px] pr-2.5'
105-
onClick={(event) => event.stopPropagation()}
60+
role='button'
61+
tabIndex={0}
62+
aria-expanded={!collapsed}
63+
aria-controls={contentProps?.id}
64+
className={cn(
65+
'flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2.5 py-[5px]',
66+
actions && 'pr-2'
67+
)}
68+
onClick={onToggleCollapse}
69+
onKeyDown={(event) => {
70+
if (event.target !== event.currentTarget) return
71+
handleKeyboardActivation(event, onToggleCollapse)
72+
}}
10673
>
107-
{actions}
74+
{typeof title === 'string' || typeof title === 'number' ? (
75+
<OverflowText
76+
label={String(title)}
77+
className='flex-1 text-[var(--text-tertiary)] text-sm'
78+
focusTarget='nearest-interactive'
79+
/>
80+
) : (
81+
<span
82+
className={cn(overflowTextClipClass, 'flex-1 text-[var(--text-tertiary)] text-sm')}
83+
>
84+
{title}
85+
</span>
86+
)}
87+
{badge}
10888
</div>
109-
)}
110-
</div>
89+
{actions && (
90+
<div
91+
role='presentation'
92+
className='flex shrink-0 items-center gap-2 py-[5px] pr-2.5'
93+
onClick={(event) => event.stopPropagation()}
94+
>
95+
{actions}
96+
</div>
97+
)}
98+
</>
99+
}
100+
>
111101
{animated ? (
112102
<Expandable expanded={!collapsed}>
113103
<ExpandableContent>{content}</ExpandableContent>
114104
</Expandable>
115105
) : (
116106
!collapsed && content
117107
)}
118-
</div>
108+
</FieldCardFrame>
119109
)
120110
}

0 commit comments

Comments
 (0)