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
14 changes: 14 additions & 0 deletions .claude/rules/emcn-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,17 @@ Declare keyboard intent on the action-owning primitive; never add document-level
- Use Radix UI primitives for accessibility. Export the component and its `variants` (when using CVA). Document with TSDoc + a usage example.

Color tokens and icon-size conventions are canonical in `.claude/rules/sim-styling.md` — follow it rather than restating.


## Ordinary Button action geometry

`Button` retains its existing appearance variants. For square actions use `iconSize`:
`compact` (24px on the spacing scale), `compact-fixed` (24px), `regular` (28px),
`roomy` (32px), or `touch` (40px). These values follow the root spacing scale;
only `compact-fixed` stays fixed when root text is enlarged.
Use `{ base: 'touch', sm: 'regular' }` for mobile/desktop targets. These props own
geometry only; colour, radius and SVG stroke continue to come from the selected
`variant` and `size`. `iconPadding` explicitly overrides the zero-padding geometry.
Use `shape='round'` for circular actions, or omit it to retain the current radius.
`size='inline'` is a 20px-high action with caption typography and compact horizontal
padding. Prefer these supported props to size, padding and radius overrides.
14 changes: 14 additions & 0 deletions .cursor/rules/emcn-components.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,17 @@ Declare keyboard intent on the action-owning primitive; never add document-level
- Use Radix UI primitives for accessibility. Export the component and its `variants` (when using CVA). Document with TSDoc + a usage example.

Color tokens and icon-size conventions are canonical in `.claude/rules/sim-styling.md` — follow it rather than restating.


## Ordinary Button action geometry

`Button` retains its existing appearance variants. For square actions use `iconSize`:
`compact` (24px on the spacing scale), `compact-fixed` (24px), `regular` (28px),
`roomy` (32px), or `touch` (40px). These values follow the root spacing scale;
only `compact-fixed` stays fixed when root text is enlarged.
Use `{ base: 'touch', sm: 'regular' }` for mobile/desktop targets. These props own
geometry only; colour, radius and SVG stroke continue to come from the selected
`variant` and `size`. `iconPadding` explicitly overrides the zero-padding geometry.
Use `shape='round'` for circular actions, or omit it to retain the current radius.
`size='inline'` is a 20px-high action with caption typography and compact horizontal
padding. Prefer these supported props to size, padding and radius overrides.
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
@@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { act, type ReactNode } from 'react'
import { act, type ComponentProps, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

Expand All @@ -19,6 +19,11 @@ vi.mock('next/navigation', () => ({
}))

vi.mock('@sim/emcn', () => ({
Button: ({
variant,
iconSize,
...props
}: ComponentProps<'button'> & { variant?: string; iconSize?: string }) => <button {...props} />,
Check: () => null,
Duplicate: () => null,
Split: () => null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { memo, useEffect, useRef, useState } from 'react'
import {
Button,
Check,
ChipModal,
ChipModalBody,
Expand All @@ -27,8 +28,6 @@ import { useForkMothershipChat } from '@/hooks/queries/mothership-chats'
import { useFolderStore } from '@/stores/folders/store'

const ICON_CLASS = 'size-[14px]'
const BUTTON_CLASS =
'flex size-[26px] items-center justify-center rounded-[6px] text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-hidden'

interface MessageActionsProps {
content: string
Expand Down Expand Up @@ -162,14 +161,15 @@ export const MessageActions = memo(function MessageActions({
{canCopyContent && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
<Button
type='button'
aria-label='Copy message'
onClick={copyToClipboard}
className={BUTTON_CLASS}
variant='quiet'
iconSize='compact-fixed'
Comment thread
BillLeoutsakosvl346 marked this conversation as resolved.
Comment thread
BillLeoutsakosvl346 marked this conversation as resolved.
>
{copied ? <Check className={ICON_CLASS} /> : <Duplicate className={ICON_CLASS} />}
</button>
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
{copied ? 'Copied message' : 'Copy message'}
Expand All @@ -180,27 +180,29 @@ export const MessageActions = memo(function MessageActions({
<>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
<Button
type='button'
aria-label='Like'
onClick={() => handleFeedbackClick('up')}
className={BUTTON_CLASS}
variant='quiet'
iconSize='compact-fixed'
>
<ThumbsUp className={ICON_CLASS} />
</button>
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Good response</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
<Button
type='button'
aria-label='Dislike'
onClick={() => handleFeedbackClick('down')}
className={BUTTON_CLASS}
variant='quiet'
iconSize='compact-fixed'
>
<ThumbsDown className={ICON_CLASS} />
</button>
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Bad response</Tooltip.Content>
</Tooltip.Root>
Expand All @@ -209,15 +211,16 @@ export const MessageActions = memo(function MessageActions({
{canFork && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
<Button
type='button'
aria-label='Fork in new chat'
onClick={handleFork}
disabled={forkChat.isPending}
className={cn(BUTTON_CLASS, forkChat.isPending && 'cursor-not-allowed opacity-50')}
variant='quiet'
iconSize='compact-fixed'
>
<Split className={cn(ICON_CLASS, 'rotate-90')} />
</button>
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Fork in new chat</Tooltip.Content>
</Tooltip.Root>
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 @@ -106,11 +106,7 @@ function FileCard({ file, isExecutionFile = false, workspaceId }: FileCardProps)
{file.type}
{file.version === undefined ? '' : ` · v${file.version}`}
</span>
<Button
variant='ghost'
className='h-[20px]! px-1.5! py-0! text-xs'
onClick={handleDownload}
>
<Button variant='ghost' size='inline' onClick={handleDownload}>
<Download className='mr-1 size-[10px]' />
Download
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export function SelectOptionsEditor({ options, onChange }: SelectOptionsEditorPr
size='sm'
onClick={() => remove(option.id)}
iconPadding='sm'
className='size-7 shrink-0'
iconSize='regular'
className='shrink-0'
aria-label={`Remove ${option.name || 'option'}`}
>
<X className='size-[12px]' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ const FilterRuleRow = memo(function FilterRuleRow({
size='sm'
onClick={() => onRemove(rule.id)}
iconPadding='sm'
className='size-7 shrink-0'
iconSize='regular'
className='shrink-0'
aria-label='Remove filter'
>
<X className='size-[12px]' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,8 @@ export const AddRowButton = React.memo(function AddRowButton({
<Button
type='button'
variant='ghost'
size='sm'
className={cn(
'h-[20px] gap-2 p-0 text-[var(--text-body)]',
blockedReason && 'cursor-not-allowed opacity-50'
)}
size='inline'
className={cn('gap-2', blockedReason && 'cursor-not-allowed opacity-50')}
aria-disabled={blockedReason ? true : undefined}
onClick={blockedReason ? undefined : onClick}
>
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
Loading
Loading