Skip to content

Commit b182120

Browse files
BillLeoutsakosvl346Bill Leoutsakos
andauthored
improvement(ui): share canvas toolbar controls (#8066)
* improvement(emcn): share ordinary action geometry in Button * improvement(ui): share canvas toolbar controls * improvement(ui): reuse Button treatments for canvas actions (#8155) Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> * fix(ui): preserve canvas action hover treatment * fix(ui): retain muted canvas icon and import conventions * refactor(emcn): keep Button hover variant self-contained --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
1 parent 1d252e5 commit b182120

3 files changed

Lines changed: 37 additions & 36 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ComponentProps } from 'react'
2+
import { forwardRef } from 'react'
3+
import { Button } from '@sim/emcn'
4+
5+
interface CanvasControlButtonProps
6+
extends Omit<
7+
ComponentProps<typeof Button>,
8+
'variant' | 'size' | 'iconSize' | 'iconPadding' | 'shape' | 'className'
9+
> {
10+
active?: boolean
11+
'aria-label': string
12+
}
13+
14+
/** Square action in the canvas navigation toolbar. */
15+
export const CanvasControlButton = forwardRef<HTMLButtonElement, CanvasControlButtonProps>(
16+
({ active = false, ...props }, ref) => (
17+
<Button {...props} ref={ref} variant={active ? 'active' : 'ghost-hover'} iconSize='regular' />
18+
)
19+
)
20+
21+
CanvasControlButton.displayName = 'CanvasControlButton'

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-controls/workflow-controls.tsx‎

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

3+
/** biome-ignore assist/source/organizeImports: Keep the local feature import after shared hooks and stores, per AGENTS.md. */
34
import { memo, useCallback, useRef, useState } from 'react'
45
import {
56
Button,
67
ChevronDown,
78
Cursor,
8-
chipHoverSurfaceClass,
99
cn,
1010
disclosureChevronClass,
1111
Hand,
@@ -32,6 +32,8 @@ import { useCanvasModeStore } from '@/stores/canvas-mode'
3232
import { useUndoRedoStore } from '@/stores/undo-redo'
3333
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
3434

35+
import { CanvasControlButton } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-controls/canvas-control-button'
36+
3537
const logger = createLogger('WorkflowControls')
3638

3739
/**
@@ -100,7 +102,7 @@ export const WorkflowControls = memo(function WorkflowControls() {
100102
* CONTENT_WINDOW_GAP; these controls measure from the canvas floor and
101103
* wall, so they take the 12 directly.
102104
*/
103-
className='absolute bottom-3 left-3 z-10 flex h-[36px] items-center gap-0.5 rounded-lg border border-[var(--border)] bg-[var(--surface-2)] p-1'
105+
className='absolute bottom-3 left-3 z-10 flex h-9 items-center gap-0.5 rounded-lg border border-[var(--border)] bg-[var(--surface-2)] p-1'
104106
onContextMenu={handleContextMenu}
105107
>
106108
{/* Canvas Mode Selector */}
@@ -109,23 +111,15 @@ export const WorkflowControls = memo(function WorkflowControls() {
109111
<PopoverTrigger asChild>
110112
<div className='flex cursor-pointer items-center gap-1'>
111113
<Tooltip.Trigger asChild>
112-
<Button
113-
aria-label={mode === 'hand' ? 'Mover' : 'Pointer'}
114-
className='size-[28px] rounded-sm p-0'
115-
variant='active'
116-
>
114+
<CanvasControlButton aria-label={mode === 'hand' ? 'Mover' : 'Pointer'} active>
117115
{mode === 'hand' ? (
118116
<Hand className='size-[14px]' />
119117
) : (
120118
<Cursor className='size-[14px]' />
121119
)}
122-
</Button>
120+
</CanvasControlButton>
123121
</Tooltip.Trigger>
124-
<Button
125-
aria-label='Change canvas mode'
126-
variant='ghost'
127-
className={cn('size-[20px] rounded-sm p-0', chipHoverSurfaceClass)}
128-
>
122+
<Button aria-label='Change canvas mode' variant='ghost-hover' size='icon'>
129123
<ChevronDown
130124
className={cn(disclosureChevronClass, isCanvasModeOpen && 'rotate-180')}
131125
/>
@@ -160,15 +154,9 @@ export const WorkflowControls = memo(function WorkflowControls() {
160154

161155
<Tooltip.Root>
162156
<Tooltip.Trigger asChild>
163-
<Button
164-
aria-label='Undo'
165-
variant='ghost'
166-
className={cn('size-[28px] rounded-sm p-0', chipHoverSurfaceClass)}
167-
onClick={undo}
168-
disabled={!canUndo}
169-
>
157+
<CanvasControlButton aria-label='Undo' onClick={undo} disabled={!canUndo}>
170158
<Undo className='size-[14px]' />
171-
</Button>
159+
</CanvasControlButton>
172160
</Tooltip.Trigger>
173161
<Tooltip.Content side='top'>
174162
<Tooltip.Shortcut keys='⌘Z'>Undo</Tooltip.Shortcut>
@@ -177,15 +165,9 @@ export const WorkflowControls = memo(function WorkflowControls() {
177165

178166
<Tooltip.Root>
179167
<Tooltip.Trigger asChild>
180-
<Button
181-
aria-label='Redo'
182-
variant='ghost'
183-
className={cn('size-[28px] rounded-sm p-0', chipHoverSurfaceClass)}
184-
onClick={redo}
185-
disabled={!canRedo}
186-
>
168+
<CanvasControlButton aria-label='Redo' onClick={redo} disabled={!canRedo}>
187169
<Redo className='size-[14px]' />
188-
</Button>
170+
</CanvasControlButton>
189171
</Tooltip.Trigger>
190172
<Tooltip.Content side='top'>
191173
<Tooltip.Shortcut keys='⌘⇧Z'>Redo</Tooltip.Shortcut>
@@ -196,14 +178,9 @@ export const WorkflowControls = memo(function WorkflowControls() {
196178

197179
<Tooltip.Root>
198180
<Tooltip.Trigger asChild>
199-
<Button
200-
aria-label='Fit to View'
201-
variant='ghost'
202-
className={cn('size-[28px] rounded-sm p-0', chipHoverSurfaceClass)}
203-
onClick={handleFitToView}
204-
>
181+
<CanvasControlButton aria-label='Fit to View' onClick={handleFitToView}>
205182
<SelectAll className='size-[14px]' />
206-
</Button>
183+
</CanvasControlButton>
207184
</Tooltip.Trigger>
208185
<Tooltip.Content side='top'>
209186
<Tooltip.Shortcut keys='⌘⇧F'>Fit to View</Tooltip.Shortcut>

‎packages/emcn/src/components/button/button.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const buttonVariants = cva(
4040
tertiary:
4141
'bg-[var(--brand-accent)] text-[var(--text-inverse)] hover-hover:text-[var(--text-inverse)] hover-hover:bg-[var(--brand-accent-hover)] dark:bg-[var(--brand-accent)] dark:hover-hover:bg-[var(--brand-accent-hover)] dark:text-[var(--text-inverse)] dark:hover-hover:text-[var(--text-inverse)]',
4242
ghost: 'text-[var(--text-secondary)] hover-hover:text-[var(--text-primary)]',
43+
'ghost-hover':
44+
'text-[var(--text-secondary)] hover-hover:text-[var(--text-primary)] hover-hover:bg-[var(--surface-hover)]',
4345
subtle:
4446
'text-[var(--text-body)] hover-hover:text-[var(--text-body)] hover-hover:bg-[var(--surface-4)]',
4547
'ghost-secondary': 'text-[var(--text-muted)] hover-hover:text-[var(--text-primary)]',
@@ -75,6 +77,7 @@ const buttonVariants = cva(
7577
*/
7678
{ size: 'icon', variant: 'quiet', className: 'text-[var(--text-icon-muted)]' },
7779
{ size: 'icon', variant: 'ghost', className: 'text-[var(--text-icon-muted)]' },
80+
{ size: 'icon', variant: 'ghost-hover', className: 'text-[var(--text-icon-muted)]' },
7881
],
7982
defaultVariants: {
8083
variant: 'default',

0 commit comments

Comments
 (0)