Skip to content

Commit 4622b05

Browse files
author
aadamgough
committed
fixed component and removed comments
1 parent 64b382e commit 4622b05

File tree

1 file changed

+56
-120
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/workflow-mcp-servers

1 file changed

+56
-120
lines changed

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

Lines changed: 56 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useCallback, useEffect, useMemo, useState } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { Check, ChevronDown, Clipboard, Plus, Search, X } from 'lucide-react'
5+
import { Check, Clipboard, Plus, Search, X } from 'lucide-react'
66
import { useParams } from 'next/navigation'
77
import {
88
Badge,
@@ -16,11 +16,6 @@ import {
1616
ModalContent,
1717
ModalFooter,
1818
ModalHeader,
19-
Popover,
20-
PopoverContent,
21-
PopoverItem,
22-
PopoverScrollArea,
23-
PopoverTrigger,
2419
Textarea,
2520
} from '@/components/emcn'
2621
import { Input, Skeleton } from '@/components/ui'
@@ -50,8 +45,8 @@ interface WorkflowTagSelectProps {
5045
}
5146

5247
/**
53-
* A tag-input style workflow selector with dropdown.
54-
* Shows selected workflows as removable tags inside the input container.
48+
* Multi-select workflow selector using Combobox.
49+
* Shows selected workflows as removable badges inside the trigger.
5550
*/
5651
function WorkflowTagSelect({
5752
workflows,
@@ -60,124 +55,68 @@ function WorkflowTagSelect({
6055
isLoading = false,
6156
disabled = false,
6257
}: WorkflowTagSelectProps) {
63-
const [open, setOpen] = useState(false)
64-
const [searchQuery, setSearchQuery] = useState('')
58+
const options: ComboboxOption[] = useMemo(() => {
59+
return workflows.map((w) => ({
60+
label: w.name,
61+
value: w.id,
62+
}))
63+
}, [workflows])
6564

66-
const availableWorkflows = useMemo(() => {
67-
return workflows.filter((w) => !selectedIds.includes(w.id))
65+
const selectedWorkflows = useMemo(() => {
66+
return workflows.filter((w) => selectedIds.includes(w.id))
6867
}, [workflows, selectedIds])
6968

70-
const filteredWorkflows = useMemo(() => {
71-
if (!searchQuery.trim()) return availableWorkflows
72-
const query = searchQuery.toLowerCase()
73-
return availableWorkflows.filter((w) => w.name.toLowerCase().includes(query))
74-
}, [availableWorkflows, searchQuery])
75-
76-
const handleSelect = (id: string) => {
77-
onSelectionChange([...selectedIds, id])
78-
setSearchQuery('')
69+
const handleRemove = (e: React.MouseEvent, id: string) => {
70+
e.preventDefault()
71+
e.stopPropagation()
72+
onSelectionChange(selectedIds.filter((i) => i !== id))
7973
}
8074

81-
const handleRemove = (id: string) => {
82-
onSelectionChange(selectedIds.filter((selectedId) => selectedId !== id))
83-
}
75+
const overlayContent = useMemo(() => {
76+
if (selectedWorkflows.length === 0) {
77+
return null
78+
}
79+
80+
return (
81+
<div className='flex items-center gap-[4px] overflow-hidden'>
82+
{selectedWorkflows.slice(0, 2).map((w) => (
83+
<Badge
84+
key={w.id}
85+
variant='outline'
86+
className='pointer-events-auto cursor-pointer gap-[4px] rounded-[6px] px-[8px] py-[2px] text-[11px]'
87+
onMouseDown={(e) => handleRemove(e, w.id)}
88+
>
89+
{w.name}
90+
<X className='h-3 w-3' />
91+
</Badge>
92+
))}
93+
{selectedWorkflows.length > 2 && (
94+
<Badge variant='outline' className='rounded-[6px] px-[8px] py-[2px] text-[11px]'>
95+
+{selectedWorkflows.length - 2}
96+
</Badge>
97+
)}
98+
</div>
99+
)
100+
}, [selectedWorkflows, selectedIds])
84101

85102
const isEmpty = workflows.length === 0
86103

104+
if (isLoading) {
105+
return <Skeleton className='h-[34px] w-full rounded-[6px]' />
106+
}
107+
87108
return (
88-
<Popover open={open && !disabled && !isEmpty} onOpenChange={setOpen}>
89-
<PopoverTrigger asChild>
90-
<div
91-
className={`flex h-[36px] cursor-pointer items-center gap-[6px] overflow-hidden rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[8px] transition-colors hover:bg-[var(--surface-7)] dark:hover:border-[var(--surface-7)] dark:hover:bg-[var(--border-1)] ${
92-
disabled ? 'cursor-not-allowed opacity-50' : ''
93-
}`}
94-
onClick={() => !disabled && !isEmpty && setOpen(true)}
95-
>
96-
{selectedIds.length === 0 ? (
97-
<span className='text-[var(--text-muted)] text-sm'>
98-
{isEmpty ? 'No deployed workflows available' : 'Select deployed workflows...'}
99-
</span>
100-
) : (
101-
<div className='flex min-w-0 flex-1 items-center gap-[6px] overflow-hidden'>
102-
{selectedIds.map((id) => {
103-
const workflow = workflows.find((w) => w.id === id)
104-
return (
105-
<div
106-
key={id}
107-
className='flex flex-shrink-0 items-center gap-[4px] rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-4)] px-[6px] py-[2px] text-[12px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
108-
>
109-
<span className='max-w-[150px] truncate'>{workflow?.name || id}</span>
110-
<button
111-
type='button'
112-
onClick={(e) => {
113-
e.stopPropagation()
114-
handleRemove(id)
115-
}}
116-
className='flex-shrink-0 text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)] focus:outline-none'
117-
aria-label={`Remove ${workflow?.name || id}`}
118-
>
119-
<X className='h-[12px] w-[12px] translate-y-[0.2px]' />
120-
</button>
121-
</div>
122-
)
123-
})}
124-
</div>
125-
)}
126-
<ChevronDown
127-
className={`ml-auto h-4 w-4 flex-shrink-0 opacity-50 transition-transform ${open ? 'rotate-180' : ''}`}
128-
/>
129-
</div>
130-
</PopoverTrigger>
131-
<PopoverContent
132-
side='bottom'
133-
align='start'
134-
sideOffset={4}
135-
className='w-[var(--radix-popover-trigger-width)] rounded-[6px] border border-[var(--border-1)] p-0'
136-
>
137-
<div className='flex items-center px-[10px] pt-[8px] pb-[4px]'>
138-
<Search className='mr-[7px] ml-[1px] h-[13px] w-[13px] shrink-0 text-[var(--text-muted)]' />
139-
<input
140-
className='w-full bg-transparent font-base text-[13px] text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:outline-none'
141-
placeholder='Search workflows...'
142-
value={searchQuery}
143-
onChange={(e) => setSearchQuery(e.target.value)}
144-
onKeyDown={(e) => {
145-
if (e.key === 'Escape') {
146-
setOpen(false)
147-
setSearchQuery('')
148-
}
149-
}}
150-
/>
151-
</div>
152-
<PopoverScrollArea className='!flex-none p-[4px]' style={{ maxHeight: '192px' }}>
153-
{isLoading ? (
154-
<div className='flex items-center justify-center py-[14px]'>
155-
<span className='font-base text-[12px] text-[var(--text-muted)]'>Loading...</span>
156-
</div>
157-
) : filteredWorkflows.length === 0 ? (
158-
<div className='py-[14px] text-center font-base text-[12px] text-[var(--text-muted)]'>
159-
{searchQuery
160-
? 'No matching workflows found'
161-
: availableWorkflows.length === 0
162-
? 'All workflows have been added'
163-
: 'No deployed workflows found'}
164-
</div>
165-
) : (
166-
<div className='space-y-[2px]'>
167-
{filteredWorkflows.map((workflow) => (
168-
<PopoverItem
169-
key={workflow.id}
170-
onClick={() => handleSelect(workflow.id)}
171-
className='cursor-pointer rounded-[4px] px-[6px] py-[6px] font-medium font-sans text-sm hover:bg-[var(--border-1)]'
172-
>
173-
<span className='truncate text-[var(--text-primary)]'>{workflow.name}</span>
174-
</PopoverItem>
175-
))}
176-
</div>
177-
)}
178-
</PopoverScrollArea>
179-
</PopoverContent>
180-
</Popover>
109+
<Combobox
110+
options={options}
111+
multiSelect
112+
multiSelectValues={selectedIds}
113+
onMultiSelectChange={onSelectionChange}
114+
placeholder={isEmpty ? 'No deployed workflows available' : 'Select deployed workflows...'}
115+
overlayContent={overlayContent}
116+
searchable
117+
searchPlaceholder='Search workflows...'
118+
disabled={disabled || isEmpty}
119+
/>
181120
)
182121
}
183122

@@ -596,7 +535,6 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
596535
}
597536

598537
interface WorkflowMcpServersProps {
599-
/** Key that when changed resets the component to list view */
600538
resetKey?: number
601539
}
602540

@@ -623,7 +561,6 @@ export function WorkflowMcpServers({ resetKey }: WorkflowMcpServersProps) {
623561
const [serverToDelete, setServerToDelete] = useState<WorkflowMcpServer | null>(null)
624562
const [deletingServers, setDeletingServers] = useState<Set<string>>(new Set())
625563

626-
// Reset to list view when resetKey changes (triggered by clicking sidebar item)
627564
useEffect(() => {
628565
if (resetKey !== undefined) {
629566
setSelectedServerId(null)
@@ -651,7 +588,6 @@ export function WorkflowMcpServers({ resetKey }: WorkflowMcpServersProps) {
651588
name: formData.name.trim(),
652589
})
653590

654-
// Add selected workflows as tools
655591
if (selectedWorkflowIds.length > 0 && server?.id) {
656592
await Promise.all(
657593
selectedWorkflowIds.map((workflowId) =>

0 commit comments

Comments
 (0)