Skip to content

Commit 0dbc209

Browse files
committed
fix(blocks): give a block one tile everywhere it is listed
A block's tile disagreed with the card it named. The canvas brands only third-party integrations and gives everything first-party its role accent, but the command palette, connection lists, tag menus and output pickers all painted straight from the catalog `bgColor` — so Webhook Trigger showed green in the palette and blue on the canvas it was about to be dropped onto, and the five roleless first-party triggers showed catalog blues where the canvas shows neutral. Read the canvas rule from one place (`hasBlockAccent`) and render it through one component (`BlockTile`), then point every surface that lists a block at them: canvas, editor header, preview, toolbar, palette, connection picker, terminal, logs trace rows, connection lists, tag menus, output pickers and the tables workflow sidebar. Folds in the duplication the split had grown: three copies of `TagIcon`, five hand-rolled tile divs, the toolbar's second encoding of the accent rule, a third icon-contrast helper on its own brightness threshold, and the dead `showColoredIcon` prop every caller passed. Tiles now share the chip radius, and the tile forces its own icon colour so popover and command rows painting `[&_svg]:text-*` can no longer wash out a pale brand tile. Large detail headers (preview panel, trace-view detail) keep their own treatment and are left for a follow-up.
1 parent f4ee41b commit 0dbc209

23 files changed

Lines changed: 339 additions & 367 deletions

File tree

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ import {
4242
getDisplayName,
4343
hasErrorInTree,
4444
hasUnhandledErrorInTree,
45-
iconColorClass,
4645
isIterationType,
4746
parseTime,
4847
} from '@/app/workspace/[workspaceId]/logs/components/log-details/utils'
48+
import { BlockTile } from '@/blocks/block-tile'
4949
import { isCustomBlockType } from '@/blocks/custom/build-config'
50+
import { getTileIconColorClass } from '@/blocks/icon-color'
5051
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
5152

5253
const DEFAULT_TREE_PANE_WIDTH = 240
@@ -331,12 +332,12 @@ const TraceTreeRow = memo(function TraceTreeRow({
331332
<div className='size-[14px] flex-shrink-0' />
332333
)}
333334
{!isIterationType(span.type) && (
334-
<div
335-
className='flex size-[14px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
336-
style={{ background: bgColor }}
337-
>
338-
{BlockIcon && <BlockIcon className={cn('size-[10px]', iconColorClass(bgColor))} />}
339-
</div>
335+
<BlockTile
336+
blockType={span.type?.toLowerCase() ?? ''}
337+
icon={BlockIcon ?? undefined}
338+
bgColor={bgColor}
339+
size='sm'
340+
/>
340341
)}
341342
<Tooltip.Root>
342343
<Tooltip.Trigger asChild>
@@ -711,7 +712,9 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
711712
className='mt-[2px] flex size-[18px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
712713
style={{ background: bgColor }}
713714
>
714-
{BlockIcon && <BlockIcon className={cn('size-[12px]', iconColorClass(bgColor))} />}
715+
{BlockIcon && (
716+
<BlockIcon className={cn('size-[12px]', getTileIconColorClass(bgColor))} />
717+
)}
715718
</div>
716719
)}
717720
<div className='flex min-w-0 flex-1 flex-col gap-0.5'>

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,6 @@ export function getBlockIconAndColor(
8686
*/
8787
const MAX_YIQ_SUM = 255_000
8888

89-
/** Returns 'text-white' for dark backgrounds, dark text for light ones. */
90-
export function iconColorClass(bgColor: string): string {
91-
const brightness = perceivedBrightness(bgColor)
92-
return brightness !== null && brightness > 160_000 / MAX_YIQ_SUM ? 'text-[#111111]' : 'text-white'
93-
}
94-
9589
/**
9690
* Near-black bgColors disappear against the dark-mode surface (--bg: #1b1b1b).
9791
* Below the brightness threshold we fall back to the neutral block color used

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichment-details/enrichment-details.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import type { EnrichmentProviderOutcome, EnrichmentRunDetail } from '@/lib/table
77
import {
88
adjustBgForContrast,
99
getBlockIconAndColor,
10-
iconColorClass,
1110
} from '@/app/workspace/[workspaceId]/logs/components/log-details/utils'
1211
import { useLogDetailsResize } from '@/app/workspace/[workspaceId]/logs/hooks'
1312
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
13+
import { getTileIconColorClass } from '@/blocks/icon-color'
1414
import { useEnrichmentDetail } from '@/hooks/queries/tables'
1515
import { formatCost } from '@/providers/utils'
1616
import { useLogDetailsUIStore } from '@/stores/logs/store'
@@ -255,7 +255,9 @@ function EnrichmentDetailsContent({
255255
style={{ background: bgColor }}
256256
>
257257
{ProviderIcon && (
258-
<ProviderIcon className={cn('size-[11px]', iconColorClass(bgColor))} />
258+
<ProviderIcon
259+
className={cn('size-[11px]', getTileIconColorClass(bgColor))}
260+
/>
259261
)}
260262
</div>
261263
<span className='min-w-0 flex-1 truncate text-[var(--text-secondary)] text-caption'>

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import type React from 'react'
43
import { useMemo, useState } from 'react'
54
import {
65
Button,
@@ -18,7 +17,7 @@ import {
1817
Tooltip,
1918
toast,
2019
} from '@sim/emcn'
21-
import { ArrowLeft, ChevronDown, Repeat, Split, SquareArrowUpRight, X } from '@sim/emcn/icons'
20+
import { ArrowLeft, ChevronDown, SquareArrowUpRight, X } from '@sim/emcn/icons'
2221
import { toError } from '@sim/utils/errors'
2322
import { generateId } from '@sim/utils/id'
2423
import { useMutation, useQueryClient } from '@tanstack/react-query'
@@ -57,8 +56,7 @@ import {
5756
RequiredLabel,
5857
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields'
5958
import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
60-
import { getBlock } from '@/blocks'
61-
import { getTileIconColorClass } from '@/blocks/icon-color'
59+
import { BlockTile } from '@/blocks/block-tile'
6260
import {
6361
useAddWorkflowGroup,
6462
useUpdateColumn,
@@ -140,8 +138,6 @@ interface BlockOutputGroup {
140138
blockId: string
141139
blockName: string
142140
blockType: string
143-
blockIcon: string | React.ComponentType<{ className?: string }>
144-
blockColor: string
145141
paths: string[]
146142
}
147143

@@ -164,25 +160,6 @@ function tableColumnTypeToInputType(colType: ColumnDefinition['type'] | undefine
164160
return columnTypeById(colType).workflowInputType
165161
}
166162

167-
const TagIcon: React.FC<{
168-
icon: string | React.ComponentType<{ className?: string }>
169-
color: string
170-
}> = ({ icon, color }) => (
171-
<div
172-
className='flex size-[14px] flex-shrink-0 items-center justify-center overflow-hidden rounded [&_img]:size-full'
173-
style={{ background: color }}
174-
>
175-
{typeof icon === 'string' ? (
176-
<span className={cn(getTileIconColorClass(color, true), 'font-bold text-micro')}>{icon}</span>
177-
) : (
178-
(() => {
179-
const IconComponent = icon
180-
return <IconComponent className={cn(getTileIconColorClass(color, true), 'size-[9px]')} />
181-
})()
182-
)}
183-
</div>
184-
)
185-
186163
/**
187164
* Right-edge sidebar for workflow group configuration. Three flows:
188165
* - create a new group (workflow + outputs + deps),
@@ -468,20 +445,10 @@ export function WorkflowSidebarBody({
468445
for (const f of flat) {
469446
let group = groupsByBlockId.get(f.blockId)
470447
if (!group) {
471-
const blockConfig = getBlock(f.blockType)
472-
const blockColor = blockConfig?.bgColor || '#2F55FF'
473-
let blockIcon: string | React.ComponentType<{ className?: string }> = f.blockName
474-
.charAt(0)
475-
.toUpperCase()
476-
if (blockConfig?.icon) blockIcon = blockConfig.icon
477-
else if (f.blockType === 'loop') blockIcon = Repeat
478-
else if (f.blockType === 'parallel') blockIcon = Split
479448
group = {
480449
blockId: f.blockId,
481450
blockName: f.blockName,
482451
blockType: f.blockType,
483-
blockIcon,
484-
blockColor,
485452
paths: [],
486453
}
487454
groupsByBlockId.set(f.blockId, group)
@@ -504,7 +471,11 @@ export function WorkflowSidebarBody({
504471
section: group.blockName,
505472
sectionElement: (
506473
<div className='flex items-center gap-1.5 px-1.5 pt-1.5 pb-1'>
507-
<TagIcon icon={group.blockIcon} color={group.blockColor} />
474+
<BlockTile
475+
blockType={group.blockType}
476+
fallbackLabel={group.blockName.charAt(0).toUpperCase()}
477+
size='sm'
478+
/>
508479
<span className='text-[var(--text-secondary)] text-caption'>{group.blockName}</span>
509480
</div>
510481
),

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

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,18 @@
11
'use client'
22

3-
import type React from 'react'
43
import { useMemo } from 'react'
54
import { ChipCombobox, Combobox, type ComboboxOptionGroup, cn } from '@sim/emcn'
6-
import { Repeat, Split } from '@sim/emcn/icons'
75
import { useShallow } from 'zustand/react/shallow'
86
import {
97
type FlattenOutputsBlockInput,
108
flattenWorkflowOutputs,
119
} from '@/lib/workflows/blocks/flatten-outputs'
12-
import { getBlock } from '@/blocks'
13-
import { getTileIconColorClass } from '@/blocks/icon-color'
10+
import { BlockTile } from '@/blocks/block-tile'
1411
import { normalizeName } from '@/executor/constants'
1512
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
1613
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1714
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1815

19-
/**
20-
* Renders a tag icon with background color for block section headers.
21-
*
22-
* @param icon - Either a letter string or a Lucide icon component
23-
* @param color - Background color for the icon container
24-
* @returns A styled icon element
25-
*/
26-
const TagIcon: React.FC<{
27-
icon: string | React.ComponentType<{ className?: string }>
28-
color: string
29-
}> = ({ icon, color }) => (
30-
<div
31-
className='flex size-[14px] flex-shrink-0 items-center justify-center overflow-hidden rounded [&_img]:size-full'
32-
style={{ background: color }}
33-
>
34-
{typeof icon === 'string' ? (
35-
<span className={cn(getTileIconColorClass(color, true), 'font-bold text-micro')}>{icon}</span>
36-
) : (
37-
(() => {
38-
const IconComponent = icon
39-
return <IconComponent className={cn(getTileIconColorClass(color, true), 'size-[9px]')} />
40-
})()
41-
)}
42-
</div>
43-
)
44-
4516
const EMPTY_OUTPUTS: string[] = []
4617

4718
/**
@@ -200,16 +171,6 @@ export function OutputSelect({
200171
return `${validOutputs.length} outputs`
201172
}, [selectedOutputs, workflowOutputs, placeholder])
202173

203-
/**
204-
* Gets the background color for a block output based on its type
205-
* @param blockType - The type of the block
206-
* @returns The hex color code for the block
207-
*/
208-
const getOutputColor = (blockType: string) => {
209-
const blockConfig = getBlock(blockType)
210-
return blockConfig?.bgColor || '#2F55FF'
211-
}
212-
213174
/**
214175
* Groups outputs by block and sorts by distance from starter block.
215176
* Returns ComboboxOptionGroup[] for use with Combobox.
@@ -261,25 +222,15 @@ export function OutputSelect({
261222

262223
return sortedGroups.map(({ blockName, outputs }) => {
263224
const firstOutput = outputs[0]
264-
const blockConfig = getBlock(firstOutput.blockType)
265-
const blockColor = getOutputColor(firstOutput.blockType)
266-
267-
let blockIcon: string | React.ComponentType<{ className?: string }> = blockName
268-
.charAt(0)
269-
.toUpperCase()
270-
271-
if (blockConfig?.icon) {
272-
blockIcon = blockConfig.icon
273-
} else if (firstOutput.blockType === 'loop') {
274-
blockIcon = Repeat
275-
} else if (firstOutput.blockType === 'parallel') {
276-
blockIcon = Split
277-
}
278225

279226
return {
280227
sectionElement: (
281228
<div className='flex items-center gap-1.5 px-1.5 py-1'>
282-
<TagIcon icon={blockIcon} color={blockColor} />
229+
<BlockTile
230+
blockType={firstOutput.blockType}
231+
fallbackLabel={blockName.charAt(0).toUpperCase()}
232+
size='sm'
233+
/>
283234
<span className='text-small'>{blockName}</span>
284235
</div>
285236
),

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/connection-block-selector/connection-block-selector.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,9 @@ export function ConnectionBlockSelector({ id, data }: NodeProps<ConnectionBlockS
433433
}}
434434
icon={result.item.icon}
435435
bgColor={result.item.bgColor}
436-
showColoredIcon
437-
workflowType={result.kind === 'block' ? result.item.type : undefined}
436+
blockType={
437+
result.kind === 'tool_operation' ? result.item.blockType : result.item.type
438+
}
438439
label={result.item.name}
439440
/>
440441
))}
@@ -453,7 +454,7 @@ export function ConnectionBlockSelector({ id, data }: NodeProps<ConnectionBlockS
453454
onSelect={() => handleToolOperationSelect(result.item)}
454455
icon={result.item.icon}
455456
bgColor={result.item.bgColor}
456-
showColoredIcon
457+
blockType={result.item.blockType}
457458
label={result.item.name}
458459
/>
459460
)
@@ -470,8 +471,7 @@ export function ConnectionBlockSelector({ id, data }: NodeProps<ConnectionBlockS
470471
}
471472
icon={result.item.icon}
472473
bgColor={result.item.bgColor}
473-
showColoredIcon
474-
workflowType={result.kind === 'block' ? result.item.type : undefined}
474+
blockType={result.item.type}
475475
label={result.item.name}
476476
/>
477477
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/connection-blocks/connection-blocks.tsx

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

33
import { useCallback, useRef, useState } from 'react'
44
import { ChevronDown, handleKeyboardActivation } from '@sim/emcn'
5-
import { Repeat, Split } from '@sim/emcn/icons'
65
import { createLogger } from '@sim/logger'
76
import clsx from 'clsx'
87
import { useShallow } from 'zustand/react/shallow'
@@ -12,8 +11,7 @@ import {
1211
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/connection-blocks/components/field-item/field-item'
1312
import type { ConnectedBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-block-connections'
1413
import { useBlockOutputFields } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-output-fields'
15-
import { getTileIconColorClass } from '@/blocks/icon-color'
16-
import { getBlock } from '@/blocks/registry'
14+
import { BlockTile } from '@/blocks/block-tile'
1715
import { normalizeName } from '@/executor/constants'
1816
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1917
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
@@ -103,8 +101,6 @@ function ConnectionItem({
103101
mergedSubBlocks,
104102
sourceBlock,
105103
}: ConnectionItemProps) {
106-
const blockConfig = getBlock(connection.type)
107-
108104
const fields = useBlockOutputFields({
109105
blockId: connection.id,
110106
blockType: connection.type,
@@ -113,19 +109,6 @@ function ConnectionItem({
113109
})
114110
const hasFields = fields.length > 0
115111

116-
let Icon = blockConfig?.icon
117-
let bgColor = blockConfig?.bgColor || '#6B7280'
118-
119-
if (!blockConfig) {
120-
if (connection.type === 'loop') {
121-
Icon = Repeat
122-
bgColor = '#2FB3FF'
123-
} else if (connection.type === 'parallel') {
124-
Icon = Split
125-
bgColor = '#FEE12B'
126-
}
127-
}
128-
129112
return (
130113
<div className='mb-0.5 last:mb-0' ref={connectionRef}>
131114
<div
@@ -144,21 +127,7 @@ function ConnectionItem({
144127
handleKeyboardActivation(event, () => onToggleExpand(connection.id))
145128
}}
146129
>
147-
<div
148-
className='[&_img]:!size-full relative flex size-[14px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm'
149-
style={{ background: bgColor }}
150-
>
151-
{Icon && (
152-
<Icon
153-
className={clsx(
154-
'transition-transform duration-200',
155-
getTileIconColorClass(bgColor),
156-
hasFields && 'group-hover:scale-110',
157-
'!size-[9px]'
158-
)}
159-
/>
160-
)}
161-
</div>
130+
<BlockTile blockType={connection.type} size='sm' />
162131
<span
163132
className={clsx(
164133
'truncate',

0 commit comments

Comments
 (0)