mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(blocks): give the detail headers the same tile as everything else (#6672)
The tile consolidation left the large detail headers on their own treatment, so a block wore one colour on the canvas and another in the header naming it: the preview panel behind the deploy modal and the logs trace detail both painted straight from the catalog `bgColor`, showing Start's catalog blue over a neutral card, and hardcoded `#2FB3FF`/`#FEE12B` for the two subflows. Adds the 18px header slot to `BlockTile` and points all three headers at it — preview panel, trace detail, and the editor header, which had been carrying its own inline copy of the accent rule. `WorkflowTypeIcon` takes an icon class so the accent chip can draw the larger glyph the header uses.
This commit is contained in:
+7
-9
@@ -47,7 +47,6 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/logs/components/log-details/utils'
|
||||
import { BlockTile } from '@/blocks/block-tile'
|
||||
import { isCustomBlockType } from '@/blocks/custom/build-config'
|
||||
import { getTileIconColorClass } from '@/blocks/icon-color'
|
||||
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
|
||||
|
||||
const DEFAULT_TREE_PANE_WIDTH = 240
|
||||
@@ -708,14 +707,13 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
|
||||
<div className='flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-3.5 pt-3 pb-4'>
|
||||
<div className='flex items-start gap-2'>
|
||||
{!isIterationType(span.type) && (
|
||||
<div
|
||||
className='mt-[2px] flex size-[18px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
|
||||
style={{ background: bgColor }}
|
||||
>
|
||||
{BlockIcon && (
|
||||
<BlockIcon className={cn('size-[12px]', getTileIconColorClass(bgColor))} />
|
||||
)}
|
||||
</div>
|
||||
<BlockTile
|
||||
blockType={span.type?.toLowerCase() ?? ''}
|
||||
icon={BlockIcon ?? undefined}
|
||||
bgColor={bgColor}
|
||||
size='lg'
|
||||
className='mt-[2px]'
|
||||
/>
|
||||
)}
|
||||
<div className='flex min-w-0 flex-1 flex-col gap-0.5'>
|
||||
<h3
|
||||
|
||||
+4
-33
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Button, ChipTag, DashedDividerLine, FieldDivider, Loader, Tooltip } from '@sim/emcn'
|
||||
import { Button, DashedDividerLine, FieldDivider, Loader, Tooltip } from '@sim/emcn'
|
||||
import {
|
||||
BookOpen,
|
||||
Check,
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
SquareArrowUpRight,
|
||||
Unlock,
|
||||
} from '@sim/emcn/icons'
|
||||
import { getWorkflowTypeAccent } from '@sim/workflow-renderer'
|
||||
import type { BlockRetryConfig } from '@sim/workflow-types/workflow'
|
||||
import { isEqual } from 'es-toolkit'
|
||||
import { useParams } from 'next/navigation'
|
||||
@@ -54,8 +53,7 @@ import {
|
||||
isBlockProtected,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils'
|
||||
import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
|
||||
import { hasBlockAccent } from '@/blocks/accent'
|
||||
import { isLightTileColor } from '@/blocks/icon-color'
|
||||
import { BlockTile } from '@/blocks/block-tile'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { useFolderMap } from '@/hooks/queries/folders'
|
||||
import { isWorkflowEffectivelyLocked } from '@/hooks/queries/utils/folder-tree'
|
||||
@@ -69,18 +67,6 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
/** Stable empty object to avoid creating new references */
|
||||
const EMPTY_SUBBLOCK_VALUES = {} as Record<string, any>
|
||||
|
||||
/**
|
||||
* Icon component for rendering block icons.
|
||||
*
|
||||
* @param icon - The icon component to render
|
||||
* @param className - Optional CSS classes
|
||||
* @returns Rendered icon or null if no icon provided
|
||||
*/
|
||||
const IconComponent = ({ icon: Icon, className }: { icon: any; className?: string }) => {
|
||||
if (!Icon) return null
|
||||
return <Icon className={className} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Editor panel component.
|
||||
* Provides editor configuration and customization options for the workflow.
|
||||
@@ -107,8 +93,6 @@ export function Editor() {
|
||||
const currentWorkflow = useCurrentWorkflow()
|
||||
const currentBlock = currentBlockId ? currentWorkflow.getBlockById(currentBlockId) : null
|
||||
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
|
||||
const typeAccent = getWorkflowTypeAccent(currentBlock?.type ?? '')
|
||||
const isIntegration = blockConfig != null && !hasBlockAccent(blockConfig.type)
|
||||
const title = currentBlock?.name || 'Editor'
|
||||
const isBlockNameSearchHighlighted =
|
||||
activeSearchTarget?.targetKind === 'block-name' && activeSearchTarget.blockId === currentBlockId
|
||||
@@ -440,21 +424,8 @@ export function Editor() {
|
||||
{/* Header */}
|
||||
<div className='mx-[-1px] flex flex-shrink-0 items-center justify-between rounded-none border border-[var(--border)] bg-[var(--surface-4)] px-3 py-1.5'>
|
||||
<div className='flex min-w-0 flex-1 items-center gap-2'>
|
||||
{(blockConfig || isSubflow) && (
|
||||
<ChipTag
|
||||
variant={isIntegration ? 'brand' : typeAccent.variant}
|
||||
tone={isIntegration ? undefined : typeAccent.tone}
|
||||
brandColor={isIntegration ? blockConfig.bgColor : undefined}
|
||||
brandForeground={
|
||||
isIntegration && isLightTileColor(blockConfig.bgColor) ? 'dark' : 'light'
|
||||
}
|
||||
className='size-[18px] justify-center px-0'
|
||||
>
|
||||
<IconComponent
|
||||
icon={isSubflow ? subflowConfig?.icon : blockConfig?.icon}
|
||||
className='size-[12px]'
|
||||
/>
|
||||
</ChipTag>
|
||||
{currentBlock && (blockConfig || isSubflow) && (
|
||||
<BlockTile blockType={currentBlock.type} size='lg' />
|
||||
)}
|
||||
{isRenaming ? (
|
||||
<input
|
||||
|
||||
+3
-37
@@ -21,9 +21,7 @@ import {
|
||||
ChevronUp,
|
||||
Clipboard,
|
||||
Expand,
|
||||
Repeat,
|
||||
Search,
|
||||
Split,
|
||||
SquareArrowUpRight,
|
||||
X,
|
||||
} from '@sim/emcn/icons'
|
||||
@@ -46,8 +44,7 @@ import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/prev
|
||||
import { useContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||
import { getBlock } from '@/blocks'
|
||||
import { BlockTile } from '@/blocks/block-tile'
|
||||
import { getTileIconColorClass } from '@/blocks/icon-color'
|
||||
import type { BlockConfig, BlockIcon, SubBlockConfig, SubBlockType } from '@/blocks/types'
|
||||
import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { navigatePath } from '@/executor/variables/resolvers/reference'
|
||||
import { useWorkflowState } from '@/hooks/queries/workflows'
|
||||
@@ -533,20 +530,6 @@ function ConnectionsSection({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon component for rendering block icons
|
||||
*/
|
||||
function IconComponent({
|
||||
icon: Icon,
|
||||
className,
|
||||
}: {
|
||||
icon: BlockIcon | undefined
|
||||
className?: string
|
||||
}) {
|
||||
if (!Icon) return null
|
||||
return <Icon className={className} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for subflow types (loop and parallel) - matches use-subflow-editor.ts
|
||||
*/
|
||||
@@ -1083,20 +1066,13 @@ function PreviewEditorContent({
|
||||
|
||||
if (isSubflow) {
|
||||
const isLoop = block.type === 'loop'
|
||||
const SubflowIcon = isLoop ? Repeat : Split
|
||||
const subflowBgColor = isLoop ? '#2FB3FF' : '#FEE12B'
|
||||
const subflowName = block.name || (isLoop ? 'Loop' : 'Parallel')
|
||||
|
||||
return (
|
||||
<div className='relative flex h-full w-full flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]'>
|
||||
{/* Header - styled like subflow header */}
|
||||
<div className='mx-[-1px] flex flex-shrink-0 items-center gap-2 rounded-b-[4px] border-[var(--border)] border-x border-b bg-[var(--surface-4)] px-3 py-1.5'>
|
||||
<div
|
||||
className='flex size-[18px] flex-shrink-0 items-center justify-center rounded-sm'
|
||||
style={{ backgroundColor: subflowBgColor }}
|
||||
>
|
||||
<SubflowIcon className={cn('size-[12px]', getTileIconColorClass(subflowBgColor))} />
|
||||
</div>
|
||||
<BlockTile blockType={block.type} size='lg' />
|
||||
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)] text-sm'>
|
||||
{subflowName}
|
||||
</span>
|
||||
@@ -1183,17 +1159,7 @@ function PreviewEditorContent({
|
||||
<div className='relative flex h-full w-full flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]'>
|
||||
{/* Header - styled like editor */}
|
||||
<div className='mx-[-1px] flex flex-shrink-0 items-center gap-2 rounded-b-[4px] border-[var(--border)] border-x border-b bg-[var(--surface-4)] px-3 py-1.5'>
|
||||
{block.type !== 'note' && (
|
||||
<div
|
||||
className='flex size-[18px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
|
||||
style={{ backgroundColor: blockConfig.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={blockConfig.icon}
|
||||
className={cn('size-[12px]', getTileIconColorClass(blockConfig.bgColor))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{block.type !== 'note' && <BlockTile blockType={block.type} size='lg' />}
|
||||
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)] text-sm'>
|
||||
{block.name || blockConfig.name}
|
||||
</span>
|
||||
|
||||
@@ -6,12 +6,23 @@ import { WorkflowTypeIcon } from '@sim/workflow-renderer'
|
||||
import { getBlockTileColor, getBlockTileIcon, hasBlockAccent } from '@/blocks/accent'
|
||||
import { getTileIconColorClass } from '@/blocks/icon-color'
|
||||
|
||||
/** Slot sizes the tile ships in: the canvas 16px chip, or 14px for dense rows. */
|
||||
/**
|
||||
* Slot sizes the tile ships in: the 18px detail header, the canvas 16px chip,
|
||||
* or 14px for dense rows.
|
||||
*/
|
||||
const TILE_SIZE_CLASS = {
|
||||
lg: 'size-[18px]',
|
||||
md: 'size-[16px]',
|
||||
sm: 'size-[14px]',
|
||||
} as const
|
||||
|
||||
/** Icon drawn inside each slot. Only the header tile takes the larger glyph. */
|
||||
const TILE_ICON_SIZE_CLASS = {
|
||||
lg: 'size-[12px]',
|
||||
md: 'size-[10px]',
|
||||
sm: 'size-[10px]',
|
||||
} as const
|
||||
|
||||
export interface BlockTileProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'style'> {
|
||||
/**
|
||||
* Block the tile represents; decides whether it takes the canvas role accent.
|
||||
@@ -54,9 +65,18 @@ export function BlockTile({
|
||||
}: BlockTileProps) {
|
||||
const Icon = icon ?? (blockType ? getBlockTileIcon(blockType) : undefined)
|
||||
const sizeClass = cn(TILE_SIZE_CLASS[size], className)
|
||||
const iconSizeClass = TILE_ICON_SIZE_CLASS[size]
|
||||
|
||||
if (blockType && Icon && hasBlockAccent(blockType)) {
|
||||
return <WorkflowTypeIcon type={blockType} Icon={Icon} className={sizeClass} {...props} />
|
||||
return (
|
||||
<WorkflowTypeIcon
|
||||
type={blockType}
|
||||
Icon={Icon}
|
||||
className={sizeClass}
|
||||
iconClassName={iconSizeClass}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const fill = bgColor ?? (blockType ? getBlockTileColor(blockType) : undefined)
|
||||
@@ -70,7 +90,8 @@ export function BlockTile({
|
||||
{Icon ? (
|
||||
<Icon
|
||||
className={cn(
|
||||
'size-[10px] transition-transform duration-100 group-hover:scale-110',
|
||||
iconSizeClass,
|
||||
'transition-transform duration-100 group-hover:scale-110',
|
||||
getTileIconColorClass(fill, true)
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -183,10 +183,18 @@ export const getWorkflowTypeAccent = (type: string) =>
|
||||
export interface WorkflowTypeIconProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
|
||||
type: string
|
||||
Icon: ComponentType<{ className?: string }>
|
||||
/** Overrides the glyph size when the chip is rendered at a non-default slot. */
|
||||
iconClassName?: string
|
||||
}
|
||||
|
||||
/** Shared compact core-block icon used by workflow discovery surfaces. */
|
||||
export function WorkflowTypeIcon({ type, Icon, className, ...props }: WorkflowTypeIconProps) {
|
||||
export function WorkflowTypeIcon({
|
||||
type,
|
||||
Icon,
|
||||
className,
|
||||
iconClassName,
|
||||
...props
|
||||
}: WorkflowTypeIconProps) {
|
||||
const typeAccent = getWorkflowTypeAccent(type)
|
||||
|
||||
return (
|
||||
@@ -197,7 +205,12 @@ export function WorkflowTypeIcon({ type, Icon, className, ...props }: WorkflowTy
|
||||
data-workflow-type-icon={type}
|
||||
{...props}
|
||||
>
|
||||
<Icon className='size-[10px] transition-transform duration-100 group-hover:scale-110' />
|
||||
<Icon
|
||||
className={cn(
|
||||
'size-[10px] transition-transform duration-100 group-hover:scale-110',
|
||||
iconClassName
|
||||
)}
|
||||
/>
|
||||
</ChipTag>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user