Skip to content

Commit b60b98e

Browse files
authored
improvement(tag-dropdown): added option to select block in tag dropdown, custom tools modal improvements, light mode fixes (#2594)
* improvement(tag-dropdown): added option to select block in tag dropdown, custom tools modal improvements, light mode fixes * fix UI bugs * remove unused components * tag drop ordering fix
1 parent 7793a6d commit b60b98e

File tree

31 files changed

+242
-1494
lines changed

31 files changed

+242
-1494
lines changed

apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ export function Knowledge() {
134134
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
135135
<div>
136136
<div className='flex items-start gap-[12px]'>
137-
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1E5A3E] bg-[#0F3D2C]'>
138-
<Database className='h-[14px] w-[14px] text-[#34D399]' />
137+
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BB377] bg-[#E8F7EE] dark:border-[#1E5A3E] dark:bg-[#0F3D2C]'>
138+
<Database className='h-[14px] w-[14px] text-[#5BB377] dark:text-[#34D399]' />
139139
</div>
140140
<h1 className='font-medium text-[18px]'>Knowledge Base</h1>
141141
</div>

apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,8 @@ export function LogsToolbar({
295295
{/* Header Section */}
296296
<div className='flex items-start justify-between'>
297297
<div className='flex items-start gap-[12px]'>
298-
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#7A5F11] bg-[#514215]'>
299-
<Library className='h-[14px] w-[14px] text-[#FBBC04]' />
298+
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#D4A843] bg-[#FDF6E3] dark:border-[#7A5F11] dark:bg-[#514215]'>
299+
<Library className='h-[14px] w-[14px] text-[#D4A843] dark:text-[#FBBC04]' />
300300
</div>
301301
<h1 className='font-medium text-[18px]'>Logs</h1>
302302
</div>

apps/sim/app/workspace/[workspaceId]/templates/templates.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ export default function Templates({
175175
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
176176
<div>
177177
<div className='flex items-start gap-[12px]'>
178-
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1A5070] bg-[#153347]'>
179-
<Layout className='h-[14px] w-[14px] text-[#33b4ff]' />
178+
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BA8D9] bg-[#E8F4FB] dark:border-[#1A5070] dark:bg-[#153347]'>
179+
<Layout className='h-[14px] w-[14px] text-[#5BA8D9] dark:text-[#33b4ff]' />
180180
</div>
181181
<h1 className='font-medium text-[18px]'>Templates</h1>
182182
</div>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx

Lines changed: 125 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client'
22

3-
import { useEffect, useMemo, useRef, useState } from 'react'
4-
import { Check } from 'lucide-react'
3+
import type React from 'react'
4+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5+
import { Check, RepeatIcon, SplitIcon } from 'lucide-react'
56
import {
67
Badge,
78
Popover,
@@ -19,6 +20,32 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
1920
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
2021
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2122

23+
/**
24+
* Renders a tag icon with background color.
25+
*
26+
* @param icon - Either a letter string or a Lucide icon component
27+
* @param color - Background color for the icon container
28+
* @returns A styled icon element
29+
*/
30+
const TagIcon: React.FC<{
31+
icon: string | React.ComponentType<{ className?: string }>
32+
color: string
33+
}> = ({ icon, color }) => (
34+
<div
35+
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
36+
style={{ background: color }}
37+
>
38+
{typeof icon === 'string' ? (
39+
<span className='!text-white font-bold text-[10px]'>{icon}</span>
40+
) : (
41+
(() => {
42+
const IconComponent = icon
43+
return <IconComponent className='!text-white size-[9px]' />
44+
})()
45+
)}
46+
</div>
47+
)
48+
2249
/**
2350
* Props for the OutputSelect component
2451
*/
@@ -71,7 +98,6 @@ export function OutputSelect({
7198
const [highlightedIndex, setHighlightedIndex] = useState(-1)
7299
const triggerRef = useRef<HTMLDivElement>(null)
73100
const popoverRef = useRef<HTMLDivElement>(null)
74-
const contentRef = useRef<HTMLDivElement>(null)
75101
const blocks = useWorkflowStore((state) => state.blocks)
76102
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
77103
const subBlockValues = useSubBlockStore((state) =>
@@ -185,8 +211,11 @@ export function OutputSelect({
185211
* @param o - The output object to check
186212
* @returns True if the output is selected, false otherwise
187213
*/
188-
const isSelectedValue = (o: { id: string; label: string }) =>
189-
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label)
214+
const isSelectedValue = useCallback(
215+
(o: { id: string; label: string }) =>
216+
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label),
217+
[selectedOutputs]
218+
)
190219

191220
/**
192221
* Gets display text for selected outputs
@@ -292,82 +321,94 @@ export function OutputSelect({
292321
* Handles output selection by toggling the selected state
293322
* @param value - The output label to toggle
294323
*/
295-
const handleOutputSelection = (value: string) => {
296-
const emittedValue =
297-
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
298-
const index = selectedOutputs.indexOf(emittedValue)
299-
300-
const newSelectedOutputs =
301-
index === -1
302-
? [...new Set([...selectedOutputs, emittedValue])]
303-
: selectedOutputs.filter((id) => id !== emittedValue)
304-
305-
onOutputSelect(newSelectedOutputs)
306-
}
324+
const handleOutputSelection = useCallback(
325+
(value: string) => {
326+
const emittedValue =
327+
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
328+
const index = selectedOutputs.indexOf(emittedValue)
329+
330+
const newSelectedOutputs =
331+
index === -1
332+
? [...new Set([...selectedOutputs, emittedValue])]
333+
: selectedOutputs.filter((id) => id !== emittedValue)
334+
335+
onOutputSelect(newSelectedOutputs)
336+
},
337+
[valueMode, workflowOutputs, selectedOutputs, onOutputSelect]
338+
)
307339

308340
/**
309341
* Handles keyboard navigation within the output list
310342
* Supports ArrowUp, ArrowDown, Enter, and Escape keys
311-
* @param e - Keyboard event
312343
*/
313-
const handleKeyDown = (e: React.KeyboardEvent) => {
314-
if (flattenedOutputs.length === 0) return
315-
316-
switch (e.key) {
317-
case 'ArrowDown':
318-
e.preventDefault()
319-
setHighlightedIndex((prev) => {
320-
const next = prev < flattenedOutputs.length - 1 ? prev + 1 : 0
321-
return next
322-
})
323-
break
324-
325-
case 'ArrowUp':
326-
e.preventDefault()
327-
setHighlightedIndex((prev) => {
328-
const next = prev > 0 ? prev - 1 : flattenedOutputs.length - 1
329-
return next
330-
})
331-
break
332-
333-
case 'Enter':
334-
e.preventDefault()
335-
if (highlightedIndex >= 0 && highlightedIndex < flattenedOutputs.length) {
336-
handleOutputSelection(flattenedOutputs[highlightedIndex].label)
337-
}
338-
break
344+
useEffect(() => {
345+
if (!open || flattenedOutputs.length === 0) return
346+
347+
const handleKeyboardEvent = (e: KeyboardEvent) => {
348+
switch (e.key) {
349+
case 'ArrowDown':
350+
e.preventDefault()
351+
e.stopPropagation()
352+
setHighlightedIndex((prev) => {
353+
if (prev === -1 || prev >= flattenedOutputs.length - 1) {
354+
return 0
355+
}
356+
return prev + 1
357+
})
358+
break
359+
360+
case 'ArrowUp':
361+
e.preventDefault()
362+
e.stopPropagation()
363+
setHighlightedIndex((prev) => {
364+
if (prev <= 0) {
365+
return flattenedOutputs.length - 1
366+
}
367+
return prev - 1
368+
})
369+
break
370+
371+
case 'Enter':
372+
e.preventDefault()
373+
e.stopPropagation()
374+
setHighlightedIndex((currentIndex) => {
375+
if (currentIndex >= 0 && currentIndex < flattenedOutputs.length) {
376+
handleOutputSelection(flattenedOutputs[currentIndex].label)
377+
}
378+
return currentIndex
379+
})
380+
break
339381

340-
case 'Escape':
341-
e.preventDefault()
342-
setOpen(false)
343-
break
382+
case 'Escape':
383+
e.preventDefault()
384+
e.stopPropagation()
385+
setOpen(false)
386+
break
387+
}
344388
}
345-
}
389+
390+
window.addEventListener('keydown', handleKeyboardEvent, true)
391+
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
392+
}, [open, flattenedOutputs, handleOutputSelection])
346393

347394
/**
348395
* Reset highlighted index when popover opens/closes
349396
*/
350397
useEffect(() => {
351398
if (open) {
352-
// Find first selected item, or start at -1
353399
const firstSelectedIndex = flattenedOutputs.findIndex((output) => isSelectedValue(output))
354400
setHighlightedIndex(firstSelectedIndex >= 0 ? firstSelectedIndex : -1)
355-
356-
// Focus the content for keyboard navigation
357-
setTimeout(() => {
358-
contentRef.current?.focus()
359-
}, 0)
360401
} else {
361402
setHighlightedIndex(-1)
362403
}
363-
}, [open, flattenedOutputs])
404+
}, [open, flattenedOutputs, isSelectedValue])
364405

365406
/**
366407
* Scroll highlighted item into view
367408
*/
368409
useEffect(() => {
369-
if (highlightedIndex >= 0 && contentRef.current) {
370-
const highlightedElement = contentRef.current.querySelector(
410+
if (highlightedIndex >= 0 && popoverRef.current) {
411+
const highlightedElement = popoverRef.current.querySelector(
371412
`[data-option-index="${highlightedIndex}"]`
372413
)
373414
if (highlightedElement) {
@@ -425,18 +466,35 @@ export function OutputSelect({
425466
minWidth={160}
426467
border
427468
disablePortal={disablePopoverPortal}
428-
onKeyDown={handleKeyDown}
429-
tabIndex={0}
430-
style={{ outline: 'none' }}
431469
>
432-
<div ref={contentRef} className='space-y-[2px]'>
470+
<div className='space-y-[2px]'>
433471
{Object.entries(groupedOutputs).map(([blockName, outputs]) => {
434-
// Calculate the starting index for this group
435472
const startIndex = flattenedOutputs.findIndex((o) => o.blockName === blockName)
436473

474+
const firstOutput = outputs[0]
475+
const blockConfig = getBlock(firstOutput.blockType)
476+
const blockColor = getOutputColor(firstOutput.blockId, firstOutput.blockType)
477+
478+
let blockIcon: string | React.ComponentType<{ className?: string }> = blockName
479+
.charAt(0)
480+
.toUpperCase()
481+
482+
if (blockConfig?.icon) {
483+
blockIcon = blockConfig.icon
484+
} else if (firstOutput.blockType === 'loop') {
485+
blockIcon = RepeatIcon
486+
} else if (firstOutput.blockType === 'parallel') {
487+
blockIcon = SplitIcon
488+
}
489+
437490
return (
438491
<div key={blockName}>
439-
<PopoverSection>{blockName}</PopoverSection>
492+
<PopoverSection>
493+
<div className='flex items-center gap-1.5'>
494+
<TagIcon icon={blockIcon} color={blockColor} />
495+
<span>{blockName}</span>
496+
</div>
497+
</PopoverSection>
440498

441499
<div className='flex flex-col gap-[2px]'>
442500
{outputs.map((output, localIndex) => {
@@ -451,17 +509,9 @@ export function OutputSelect({
451509
onClick={() => handleOutputSelection(output.label)}
452510
onMouseEnter={() => setHighlightedIndex(globalIndex)}
453511
>
454-
<div
455-
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
456-
style={{
457-
backgroundColor: getOutputColor(output.blockId, output.blockType),
458-
}}
459-
>
460-
<span className='font-bold text-[10px] text-white'>
461-
{blockName.charAt(0).toUpperCase()}
462-
</span>
463-
</div>
464-
<span className='min-w-0 flex-1 truncate'>{output.path}</span>
512+
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)]'>
513+
{output.path}
514+
</span>
465515
{isSelectedValue(output) && <Check className='h-3 w-3 flex-shrink-0' />}
466516
</PopoverItem>
467517
)

0 commit comments

Comments
 (0)