Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')}
>
<ArrowUp className='size-[13px]' />
Expand All @@ -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')}
>
<ArrowDown className='size-[13px]' />
Expand All @@ -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}
>
<X className='size-[13px]' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]'
>
<SquareArrowUpRight className='size-[12px]' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]',
Expand Down Expand Up @@ -472,6 +472,7 @@ export const ActionBar = memo(
}
if (canRunBlock) handleRunFromBlockClick()
}}
iconSize='compact-fixed'
className={cn(getActionButtonStyles('run'), isWorkflowRunning && 'group/run')}
disabled={!canStopWorkflow && !canRunBlock}
>
Expand Down Expand Up @@ -517,6 +518,7 @@ export const ActionBar = memo(
collaborativeBatchToggleBlockEnabled([blockId])
}
}}
iconSize='compact-fixed'
className={getActionButtonStyles('enabled')}
disabled={
isWorkflowRunning ||
Expand Down Expand Up @@ -553,6 +555,7 @@ export const ActionBar = memo(
<DropdownMenuTrigger asChild>
<Button
variant='ghost'
iconSize='compact-fixed'
className={getActionButtonStyles('color')}
disabled={
isWorkflowRunning ||
Expand Down Expand Up @@ -618,6 +621,7 @@ export const ActionBar = memo(
collaborativeBatchToggleLocked([blockId])
}
}}
iconSize='compact-fixed'
className={getActionButtonStyles('lock')}
disabled={isWorkflowRunning || disabled || (isLocked && isParentLocked)}
>
Expand Down Expand Up @@ -650,6 +654,7 @@ export const ActionBar = memo(
handleDuplicateBlock()
}
}}
iconSize='compact-fixed'
className={getActionButtonStyles('duplicate')}
disabled={isWorkflowRunning || disabled || isLocked || isParentLocked}
>
Expand Down Expand Up @@ -682,6 +687,7 @@ export const ActionBar = memo(
)
}
}}
iconSize='compact-fixed'
className={getActionButtonStyles('remove')}
disabled={
isWorkflowRunning ||
Expand Down Expand Up @@ -717,6 +723,7 @@ export const ActionBar = memo(
collaborativeBatchRemoveBlocks([blockId])
}
}}
iconSize='compact-fixed'
className={getActionButtonStyles('delete')}
disabled={isWorkflowRunning || disabled || isLocked || isParentLocked}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ function SingleFileSelector({
aria-label='Remove file'
type='button'
variant='ghost'
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10 size-6 p-0'
iconSize='compact'
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10'
onClick={onClear}
disabled={isDeleting}
>
Expand Down Expand Up @@ -766,7 +767,8 @@ export function FileUpload({
aria-label='Remove file'
type='button'
variant='ghost'
className='-translate-y-1/2 absolute top-1/2 right-[4px] size-6 p-0'
iconSize='compact'
className='-translate-y-1/2 absolute top-1/2 right-[4px]'
onClick={(e) => handleRemoveFile(file, e)}
disabled={isDeleting}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,8 @@ export function MessagesInput({
deleteMessage(index)
}}
disabled={disabled}
className='-my-1 -mr-1 size-6 p-0'
iconSize='compact'
className='-my-1 -mr-1'
aria-label='Delete message'
>
<Trash className='size-3' />
Expand All @@ -673,7 +674,8 @@ export function MessagesInput({
moveMessageUp(index)
}}
disabled={disabled || index === 0}
className='-my-1 -mr-1 size-6 p-0'
iconSize='compact'
className='-my-1 -mr-1'
aria-label='Move message up'
>
<ChevronUp className='size-3' />
Expand All @@ -685,7 +687,8 @@ export function MessagesInput({
moveMessageDown(index)
}}
disabled={disabled || index === currentMessages.length - 1}
className='-my-1 -mr-1 size-6 p-0'
iconSize='compact'
className='-my-1 -mr-1'
aria-label='Move message down'
>
<ChevronDown className='size-3' />
Expand All @@ -699,7 +702,8 @@ export function MessagesInput({
addMessageAfter(index)
}}
disabled={disabled}
className='-mr-1.5 -my-1 size-6 p-0'
iconSize='compact'
className='-mr-1.5 -my-1'
aria-label='Add message below'
>
<Plus className='size-3.5' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ export function SelectorCombobox({
aria-label='Clear selection'
type='button'
variant='ghost'
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10 size-6 p-0'
iconSize='compact'
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10'
onClick={handleClear}
>
<X className='size-4 opacity-50 hover-hover:opacity-100' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,8 @@ export function Editor() {
type='button'
variant='ghost'
onClick={handleOpenChildWorkflow}
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)]'
>
<SquareArrowUpRight className='size-[12px]' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<Expand className='size-[12px]' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const bulkActionButtonVariants = cva(
}
)

export interface BulkActionButtonProps extends Omit<ButtonProps, 'variant' | 'size'> {
export interface BulkActionButtonProps
extends Omit<ButtonProps, 'variant' | 'size' | 'iconPadding' | 'iconSize'> {
/** Accessible name for the icon action; tooltip content is supplied separately. */
'aria-label': string
/**
Expand Down
67 changes: 67 additions & 0 deletions packages/emcn/src/components/button/button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** @vitest-environment node */
/** biome-ignore assist/source/organizeImports: Preserve the documented core/external/UI import order. */
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it } from 'vitest'
import { Button } from '@sim/emcn'

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 = <svg className='size-[14px]' strokeWidth={1.55} aria-hidden='true' />
const before = renderToStaticMarkup(
<Button
{...treatment}
type='button'
aria-label='Previous match'
className={`${previousClass} shrink-0`}
>
{icon}
</Button>
)
const after = renderToStaticMarkup(
<Button
{...treatment}
type='button'
aria-label='Previous match'
iconSize={iconSize}
className='shrink-0'
>
{icon}
</Button>
)
expect(normalizeClasses(after)).toBe(normalizeClasses(before))
}
)
}

it('allows explicit padding and consumer width to take precedence', () => {
const markup = renderToStaticMarkup(
<Button iconSize='compact-fixed' iconPadding='sm' className='w-[40px]!' aria-label='Run' />
)
expect(markup).toContain('size-[24px]')
expect(markup).toContain('w-[40px]!')
expect(markup).toContain('p-1')
expect(markup).not.toContain('p-0')
expect(markup).not.toContain('iconSize')
expect(markup).not.toContain('iconPadding')
})
})
17 changes: 15 additions & 2 deletions packages/emcn/src/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const buttonVariants = cva(
md: 'px-2 py-1.5 text-[length:12px]',
icon: 'size-[20px] rounded-sm p-0 [&_svg]:[stroke-width:1.25]',
},
iconSize: {
compact: 'size-6 p-0',
'compact-fixed': 'size-[24px] p-0',
},
iconPadding: {
sm: 'p-1',
md: 'p-1.5',
Expand All @@ -75,6 +79,15 @@ const buttonVariants = cva(
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
/**
* Square icon-action geometry without changing the selected size's typography,
* corner radius, icon stroke or color. `compact` follows the spacing scale
* (24px at the default root font size); `compact-fixed` stays at 24px.
* Both remove padding; an explicit iconPadding or className can override it.
* Omit to retain the selected size's geometry.
* @example <Button variant='ghost' iconSize='compact' aria-label='Remove'><X /></Button>
*/
iconSize?: VariantProps<typeof buttonVariants>['iconSize']
/**
* Symmetric padding for icon actions whose content or layout determines their size.
* Preserves the selected size's typography, corner radius and icon stroke.
Expand All @@ -85,11 +98,11 @@ export interface ButtonProps
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, iconPadding, ...props }, ref) => {
({ className, variant, size, iconSize, iconPadding, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size, iconPadding }), className)}
className={cn(buttonVariants({ variant, size, iconSize, iconPadding }), className)}
{...props}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const composerActionButtonVariants = cva('rounded-full p-0', {
defaultVariants: { size: 'md', active: true },
})

export interface ComposerActionButtonProps extends Omit<ButtonProps, 'variant' | 'size'> {
export interface ComposerActionButtonProps
extends Omit<ButtonProps, 'variant' | 'size' | 'iconPadding' | 'iconSize'> {
/** Accessible name for the caller's icon action. */
'aria-label': string
/** 28px by default; `sm` retains compact chat's 22px geometry and hover treatment. */
Expand Down
3 changes: 2 additions & 1 deletion packages/workflow-renderer/src/note/note-block-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,8 @@ export function NoteBlockView({
event.stopPropagation()
onExpandedChange(!isExpanded)
}}
className='nodrag nopan nowheel pointer-events-none ml-1 size-[24px] shrink-0 rounded-md border-none bg-transparent p-0 text-current opacity-0 transition-[background-color,color,opacity,transform] duration-150 hover-hover:bg-current/10 hover-hover:opacity-100 active:scale-[0.96] group-hover:pointer-events-auto group-hover:opacity-70 group-data-[node-selected]:pointer-events-auto group-data-[node-selected]:opacity-70'
iconSize='compact-fixed'
className='nodrag nopan nowheel pointer-events-none ml-1 shrink-0 rounded-md border-none bg-transparent text-current opacity-0 transition-[background-color,color,opacity,transform] duration-150 hover-hover:bg-current/10 hover-hover:opacity-100 active:scale-[0.96] group-hover:pointer-events-auto group-hover:opacity-70 group-data-[node-selected]:pointer-events-auto group-data-[node-selected]:opacity-70'
>
<span className='relative size-[14px]'>
<Expand
Expand Down
Loading