From 366afbb4a7959365be8de2dccfc2e38442ae09ed Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Fri, 18 Sep 2026 16:25:31 -0700 Subject: [PATCH 1/8] refactor(emcn): centralize compact icon button sizing --- .../components/find-bar/find-bar.tsx | 12 ++-- .../browser-session/browser-find-bar.tsx | 9 ++- .../workflow-sidebar/workflow-sidebar.tsx | 3 +- .../components/action-bar/action-bar.tsx | 9 ++- .../components/file-upload/file-upload.tsx | 6 +- .../messages-input/messages-input.tsx | 12 ++-- .../selector-combobox/selector-combobox.tsx | 3 +- .../panel/components/editor/editor.tsx | 3 +- .../preview-editor/preview-editor.tsx | 3 +- .../src/components/button/button.test.tsx | 66 +++++++++++++++++++ .../emcn/src/components/button/button.tsx | 17 ++++- .../src/note/note-block-view.tsx | 3 +- 12 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 packages/emcn/src/components/button/button.test.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx b/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx index ceb2b0b709b..6ec4de4377e 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx @@ -129,7 +129,8 @@ export const FindBar = memo(function FindBar({ type='button' variant='ghost' size='icon' - className='size-6 shrink-0' + iconSize='compact' + className='shrink-0' aria-label={showReplace ? 'Hide replace' : 'Show replace'} aria-expanded={showReplace} onClick={() => setShowReplace((visible) => !visible)} @@ -179,7 +180,8 @@ export const FindBar = memo(function FindBar({ type='button' variant='ghost' size='icon' - className='size-6 shrink-0' + iconSize='compact' + className='shrink-0' aria-label='Previous match' title='Previous match (Shift+Enter)' disabled={!navEnabled} @@ -191,7 +193,8 @@ export const FindBar = memo(function FindBar({ type='button' variant='ghost' size='icon' - className='size-6 shrink-0' + iconSize='compact' + className='shrink-0' aria-label='Next match' title='Next match (Enter)' disabled={!navEnabled} @@ -203,7 +206,8 @@ export const FindBar = memo(function FindBar({ type='button' variant='ghost' size='icon' - className='size-6 shrink-0' + iconSize='compact' + className='shrink-0' aria-label='Close find' title='Close (Esc)' onClick={onClose} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx index a3f9e23198d..8313c2c549e 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx @@ -106,7 +106,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro size='sm' aria-label='Previous match' disabled={!result?.matches} - className='size-[24px] shrink-0 p-0' + iconSize='compact-fixed' + className='shrink-0' onClick={() => step('back')} > @@ -117,7 +118,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro size='sm' aria-label='Next match' disabled={!result?.matches} - className='size-[24px] shrink-0 p-0' + iconSize='compact-fixed' + className='shrink-0' onClick={() => step('forward')} > @@ -127,7 +129,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro variant='ghost-secondary' size='sm' aria-label='Close find bar' - className='size-[24px] shrink-0 p-0' + iconSize='compact-fixed' + className='shrink-0' onClick={dismiss} > diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx index 18b04cfd5b2..2c53382bf54 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx @@ -730,7 +730,8 @@ export function WorkflowSidebarBody({ 'noopener,noreferrer' ) } - className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]' + iconSize='compact-fixed' + className='absolute right-[6px] bottom-1.5 z-10 cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] hover-hover:bg-[var(--surface-4)]' > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx index c42c236d1c3..4bd3f1c6c00 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx @@ -36,7 +36,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store' const DEFAULT_DUPLICATE_OFFSET = { x: 50, y: 50 } const ACTION_BUTTON_STYLES = [ - 'size-[24px] rounded-md p-0', + 'rounded-md', 'border-none bg-transparent text-[var(--text-icon)]', 'hover-hover:bg-[var(--surface-5)] hover-hover:text-[var(--text-primary)]!', 'dark:hover-hover:bg-[var(--surface-4)]', @@ -472,6 +472,7 @@ export const ActionBar = memo( } if (canRunBlock) handleRunFromBlockClick() }} + iconSize='compact-fixed' className={cn(getActionButtonStyles('run'), isWorkflowRunning && 'group/run')} disabled={!canStopWorkflow && !canRunBlock} > @@ -517,6 +518,7 @@ export const ActionBar = memo( collaborativeBatchToggleBlockEnabled([blockId]) } }} + iconSize='compact-fixed' className={getActionButtonStyles('enabled')} disabled={ isWorkflowRunning || @@ -553,6 +555,7 @@ export const ActionBar = memo( diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx index 6a1ce331611..a583115eaa6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx @@ -1398,7 +1398,8 @@ function PreviewEditorContent({ type='button' variant='ghost' onClick={handleExpandChildWorkflow} - className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]' + iconSize='compact-fixed' + className='absolute right-[6px] bottom-1.5 z-10 cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] hover-hover:bg-[var(--surface-4)]' > {isExecutionMode && onDrillDown ? ( diff --git a/packages/emcn/src/components/button/button.test.tsx b/packages/emcn/src/components/button/button.test.tsx new file mode 100644 index 00000000000..154820a5b0b --- /dev/null +++ b/packages/emcn/src/components/button/button.test.tsx @@ -0,0 +1,66 @@ +/** @vitest-environment node */ +import { Button } from '@sim/emcn' +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' + +function normalizeClasses(markup: string) { + return markup.replace( + /class="([^"]+)"/g, + (_, classes: string) => `class="${classes.split(/\s+/).sort().join(' ')}"` + ) +} + +/** The three existing Button treatments used by compact product actions. */ +const TREATMENTS = [ + { size: undefined, variant: 'ghost' }, + { size: 'sm', variant: 'ghost-secondary' }, + { size: 'icon', variant: 'ghost' }, +] as const + +describe('Button iconSize', () => { + for (const [iconSize, previousClass] of [ + ['compact', 'size-6 p-0'], + ['compact-fixed', 'size-[24px] p-0'], + ] as const) { + it.each(TREATMENTS)( + `preserves the ${iconSize} treatment with size=$size and variant=$variant`, + (treatment) => { + const icon = + const before = renderToStaticMarkup( + + ) + const after = renderToStaticMarkup( + + ) + expect(normalizeClasses(after)).toBe(normalizeClasses(before)) + } + ) + } + + it('allows explicit padding and consumer width to take precedence', () => { + const markup = renderToStaticMarkup( + + */ + iconSize?: VariantProps['iconSize'] /** * Symmetric padding for icon actions whose content or layout determines their size. * Preserves the selected size's typography, corner radius and icon stroke. @@ -85,11 +98,11 @@ export interface ButtonProps } const Button = forwardRef( - ({ className, variant, size, iconPadding, ...props }, ref) => { + ({ className, variant, size, iconSize, iconPadding, ...props }, ref) => { return ( - - + setShowAdvanced((v) => !v)}> + {showAdvanced ? 'Hide additional fields' : 'Show additional fields'} + {showAdvanced && ( <> - - - - + + {displayAdvancedOptions + ? 'Hide additional fields' + : 'Show additional fields'} + )} {hasAdditionalFields && !canEditBlock && displayAdvancedOptions && (
diff --git a/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx b/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx new file mode 100644 index 00000000000..ed3a624673d --- /dev/null +++ b/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx @@ -0,0 +1,64 @@ +/** + * @vitest-environment jsdom + */ +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { FieldDisclosure } from './field-divider' + +let root: Root | null = null +let container: HTMLDivElement | null = null + +afterEach(() => { + act(() => root?.unmount()) + container?.remove() + root = null + container = null +}) + +describe('FieldDisclosure', () => { + it('leaves expansion to the caller and does not submit its surrounding form', () => { + const onClick = vi.fn() + const onSubmit = vi.fn((event) => event.preventDefault()) + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + const render = (expanded: boolean, disabled = false) => ( +
+ + {expanded ? 'Hide additional fields' : 'Show additional fields'} + + +
+ ) + act(() => root?.render(render(false))) + const button = container.querySelector('button')! + expect(button.getAttribute('aria-expanded')).toBe('false') + expect(button.getAttribute('aria-controls')).toBe('additional-fields') + act(() => { + button.focus() + button.click() + }) + expect(document.activeElement).toBe(button) + expect(onClick).toHaveBeenCalledTimes(1) + expect(onSubmit).not.toHaveBeenCalled() + expect(button.getAttribute('aria-expanded')).toBe('false') + + act(() => root?.render(render(true))) + expect(button.getAttribute('aria-expanded')).toBe('true') + expect(button.textContent).toBe('Hide additional fields') + expect(container.querySelector('#additional-fields')?.hidden).toBe(false) + + act(() => root?.render(render(true, true))) + act(() => button.click()) + expect(onClick).toHaveBeenCalledTimes(1) + expect(onSubmit).not.toHaveBeenCalled() + }) +}) diff --git a/packages/emcn/src/components/field-divider/field-divider.tsx b/packages/emcn/src/components/field-divider/field-divider.tsx index 7408a21360f..731dac9995e 100644 --- a/packages/emcn/src/components/field-divider/field-divider.tsx +++ b/packages/emcn/src/components/field-divider/field-divider.tsx @@ -1,4 +1,6 @@ +import { ChevronDown } from '../../icons' import { cn } from '../../lib/cn' +import { Button, type ButtonProps } from '../button/button' const DASHED_DIVIDER_STYLE = { backgroundImage: @@ -54,4 +56,40 @@ function FieldDivider({ className, subblockMarker = false, ...props }: FieldDivi ) } -export { DashedDividerLine, FieldDivider } +export interface FieldDisclosureProps + extends Pick { + expanded: boolean +} + +/** + * Controlled disclosure between field groups. The caller owns the label, + * expanded state and fields; this supplies the divider lines and rotating chevron. + * + * @example + * + * {expanded ? 'Hide additional fields' : 'Show additional fields'} + * + */ +function FieldDisclosure({ expanded, children, ...props }: FieldDisclosureProps) { + return ( +
+ + + +
+ ) +} + +export { DashedDividerLine, FieldDisclosure, FieldDivider } diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts index 6628b0a2d1b..29607eea0a6 100644 --- a/packages/emcn/src/components/index.ts +++ b/packages/emcn/src/components/index.ts @@ -153,7 +153,12 @@ export { dropdownMenuRowClass, } from './dropdown-menu/dropdown-menu' export { Expandable, ExpandableContent } from './expandable/expandable' -export { DashedDividerLine, FieldDivider } from './field-divider/field-divider' +export { + DashedDividerLine, + FieldDisclosure, + type FieldDisclosureProps, + FieldDivider, +} from './field-divider/field-divider' export { Info } from './info/info' export { InfoCard, From bdeefd3b9dbb375eebd9739315dd3394dcd9fddd Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Sat, 19 Sep 2026 14:35:37 -0700 Subject: [PATCH 6/8] fix(ui): show keyboard focus on field disclosures --- packages/emcn/src/components/field-divider/field-divider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/emcn/src/components/field-divider/field-divider.tsx b/packages/emcn/src/components/field-divider/field-divider.tsx index 731dac9995e..4b15d867f80 100644 --- a/packages/emcn/src/components/field-divider/field-divider.tsx +++ b/packages/emcn/src/components/field-divider/field-divider.tsx @@ -80,7 +80,7 @@ function FieldDisclosure({ expanded, children, ...props }: FieldDisclosureProps) variant='ghost' size={null} aria-expanded={expanded} - className='gap-1.5 whitespace-nowrap p-0 text-small' + className='gap-1.5 whitespace-nowrap p-0 text-small focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]' > {children} Date: Sat, 19 Sep 2026 17:55:10 -0700 Subject: [PATCH 7/8] improvement(ui): share table sidebar headers (#8048) Co-authored-by: Bill Leoutsakos --- .../column-config-sidebar.tsx | 19 ++++------ .../enrichments-sidebar/enrichment-config.tsx | 28 ++++++-------- .../enrichments-sidebar.tsx | 36 +++++++----------- .../table-sidebar-header.tsx | 37 +++++++++++++++++++ .../workflow-sidebar/workflow-sidebar.tsx | 28 ++++++-------- 5 files changed, 80 insertions(+), 68 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx index abf3f9391a0..2ad4ebc773b 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx @@ -25,6 +25,10 @@ import { FieldError, RequiredLabel, } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields' +import { + TableSidebarHeader, + TableSidebarHeaderAction, +} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header' import { useAddTableColumn, useUpdateColumn } from '@/hooks/queries/tables' import { SelectOptionsEditor } from '../select-field' import { columnTypeOptionsForTable } from './column-types' @@ -263,19 +267,12 @@ function ColumnConfigBody({ return (
-
+

Configure column

- -
+ +
{/* `disabled` on the fieldset reaches every native control inside, diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx index f411281d21e..e158e4ab159 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx @@ -21,6 +21,10 @@ import type { ColumnDefinition, WorkflowGroup, WorkflowGroupOutput } from '@/lib import { columnMatchesRef, getColumnId } from '@/lib/table/column-keys' import { deriveOutputColumnName } from '@/lib/table/column-naming' import { FieldError } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields' +import { + TableSidebarHeader, + TableSidebarHeaderAction, +} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header' import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types' import { useAddWorkflowGroup, @@ -232,31 +236,21 @@ export function EnrichmentConfig({ return (
-
+
- +
- -
+ +
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx index 0aebd34cb4c..421055477fc 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx @@ -1,9 +1,13 @@ 'use client' import { useState } from 'react' -import { Button, ChipInput, cn } from '@sim/emcn' +import { ChipInput, cn } from '@sim/emcn' import { Search, X } from '@sim/emcn/icons' import type { ColumnDefinition, WorkflowGroup } from '@/lib/table' +import { + TableSidebarHeader, + TableSidebarHeaderAction, +} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header' import { ALL_ENRICHMENTS } from '@/enrichments' import { getEnrichment } from '@/enrichments/registry' import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types' @@ -71,19 +75,12 @@ function EnrichmentsSidebarBody({ if (editGroup && !editEnrichment) { return (
-
+

Enrichment

- -
+ +

This enrichment ("{editGroup.enrichmentId}") is no longer available. Delete the column @@ -118,19 +115,12 @@ function EnrichmentsSidebarBody({ return (

-
+

Enrichments

- -
+ +
+ {children} +
+ ) +} + +interface TableSidebarHeaderActionProps extends ButtonHTMLAttributes { + 'aria-label': string +} + +export const TableSidebarHeaderAction = forwardRef< + HTMLButtonElement, + TableSidebarHeaderActionProps +>(({ className, ...props }, ref) => ( + + )}

- -
+ +
{/* Single-output mode renames this column directly. */} From 2d56098b9be09edeebde89b2b055bf8d7bffc97b Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos <157128530+BillLeoutsakosvl346@users.noreply.github.com> Date: Sun, 20 Sep 2026 20:31:46 -0700 Subject: [PATCH 8/8] improvement(ui): use shared sizing for small icon actions (#8090) * improvement(ui): use shared sizing for small icon actions * fix(ui): inherit the shared unlock icon color --------- Co-authored-by: Bill Leoutsakos --- .../chat/components/message/components/file-download.tsx | 2 +- .../app/(interfaces)/chat/components/message/message.tsx | 2 +- .../components/panel/components/editor/editor.tsx | 8 ++++---- .../components/panel/components/toolbar/toolbar.tsx | 7 +------ .../w/[workflowId]/components/panel/panel.tsx | 4 ++-- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx b/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx index 938ea3f2b3c..0e3c40e1f15 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx @@ -254,7 +254,7 @@ export function ChatFileDownloadAll({ files }: ChatFileDownloadAllProps) { variant='ghost-secondary' onClick={handleDownloadAll} disabled={isDownloading} - className='p-0' + size='icon' > {isDownloading ? ( diff --git a/apps/sim/app/(interfaces)/chat/components/message/message.tsx b/apps/sim/app/(interfaces)/chat/components/message/message.tsx index 9458ed16be7..1b99ccf96cc 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/message.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/message.tsx @@ -274,7 +274,7 @@ export const ClientChatMessage = memo(function ClientChatMessage({ ) : (
@@ -505,7 +505,7 @@ export function Editor() { ) : ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 5f894cfc5f3..5337e108a8d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -915,7 +915,7 @@ export const Panel = memo(function Panel() {