Skip to content

Commit 06782a8

Browse files
committed
improvement(ui/ux): action bar, panel, tooltip, dragging, invite modal
1 parent 75da0c3 commit 06782a8

File tree

20 files changed

+286
-109
lines changed

20 files changed

+286
-109
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client'
2+
3+
import { Tooltip } from '@/components/emcn'
4+
5+
interface TooltipProviderProps {
6+
children: React.ReactNode
7+
}
8+
9+
export function TooltipProvider({ children }: TooltipProviderProps) {
10+
return <Tooltip.Provider>{children}</Tooltip.Provider>
11+
}

apps/sim/app/_styles/globals.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@
5858
pointer-events: none !important;
5959
}
6060

61+
/**
62+
* Workflow canvas cursor styles
63+
* Override React Flow's default selection cursor based on canvas mode
64+
*/
65+
.workflow-container.canvas-mode-cursor .react-flow__pane,
66+
.workflow-container.canvas-mode-cursor .react-flow__selectionpane {
67+
cursor: default !important;
68+
}
69+
70+
.workflow-container.canvas-mode-hand .react-flow__pane,
71+
.workflow-container.canvas-mode-hand .react-flow__selectionpane {
72+
cursor: grab !important;
73+
}
74+
75+
.workflow-container.canvas-mode-hand .react-flow__pane:active,
76+
.workflow-container.canvas-mode-hand .react-flow__selectionpane:active {
77+
cursor: grabbing !important;
78+
}
79+
6180
/**
6281
* Selected node ring indicator
6382
* Uses a pseudo-element overlay to match the original behavior (absolute inset-0 z-40)

apps/sim/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler'
1111
import { QueryProvider } from '@/app/_shell/providers/query-provider'
1212
import { SessionProvider } from '@/app/_shell/providers/session-provider'
1313
import { ThemeProvider } from '@/app/_shell/providers/theme-provider'
14+
import { TooltipProvider } from '@/app/_shell/providers/tooltip-provider'
1415
import { season } from '@/app/_styles/fonts/season/season'
1516

1617
export const viewport: Viewport = {
@@ -194,7 +195,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
194195
<ThemeProvider>
195196
<QueryProvider>
196197
<SessionProvider>
197-
<BrandedLayout>{children}</BrandedLayout>
198+
<TooltipProvider>
199+
<BrandedLayout>{children}</BrandedLayout>
200+
</TooltipProvider>
198201
</SessionProvider>
199202
</QueryProvider>
200203
</ThemeProvider>
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
'use client'
22

3-
import { Tooltip } from '@/components/emcn'
43
import { season } from '@/app/_styles/fonts/season/season'
54

65
export default function TemplatesLayoutClient({ children }: { children: React.ReactNode }) {
76
return (
8-
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
9-
<div className={`${season.variable} relative flex min-h-screen flex-col font-season`}>
10-
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
11-
{children}
12-
</div>
13-
</Tooltip.Provider>
7+
<div className={`${season.variable} relative flex min-h-screen flex-col font-season`}>
8+
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
9+
{children}
10+
</div>
1411
)
1512
}

apps/sim/app/workspace/[workspaceId]/layout.tsx

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

3-
import { Tooltip } from '@/components/emcn'
43
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
54
import { ProviderModelsLoader } from '@/app/workspace/[workspaceId]/providers/provider-models-loader'
65
import { SettingsLoader } from '@/app/workspace/[workspaceId]/providers/settings-loader'
@@ -13,16 +12,14 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
1312
<SettingsLoader />
1413
<ProviderModelsLoader />
1514
<GlobalCommandsProvider>
16-
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
17-
<div className='flex h-screen w-full bg-[var(--bg)]'>
18-
<WorkspacePermissionsProvider>
19-
<div className='shrink-0' suppressHydrationWarning>
20-
<Sidebar />
21-
</div>
22-
{children}
23-
</WorkspacePermissionsProvider>
24-
</div>
25-
</Tooltip.Provider>
15+
<div className='flex h-screen w-full bg-[var(--bg)]'>
16+
<WorkspacePermissionsProvider>
17+
<div className='shrink-0' suppressHydrationWarning>
18+
<Sidebar />
19+
</div>
20+
{children}
21+
</WorkspacePermissionsProvider>
22+
</div>
2623
</GlobalCommandsProvider>
2724
</>
2825
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { createLogger } from '@sim/logger'
55
import { useReactFlow } from 'reactflow'
66
import {
77
Button,
8+
ChevronDown,
89
Cursor,
910
Expand,
1011
Hand,
1112
Popover,
1213
PopoverAnchor,
1314
PopoverContent,
1415
PopoverItem,
16+
PopoverTrigger,
1517
Redo,
1618
Tooltip,
1719
Undo,
@@ -48,6 +50,7 @@ export function ActionBar() {
4850
const canRedo = stack.redo.length > 0
4951

5052
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null)
53+
const [isCanvasModeOpen, setIsCanvasModeOpen] = useState(false)
5154
const menuRef = useRef<HTMLDivElement>(null)
5255

5356
const handleContextMenu = (e: React.MouseEvent) => {
@@ -72,42 +75,59 @@ export function ActionBar() {
7275
return (
7376
<>
7477
<div
75-
className='fixed bottom-[calc(var(--terminal-height)+16px)] left-[calc(var(--sidebar-width)+16px)] z-10 flex h-[36px] items-center gap-[2px] rounded-[8px] border border-[var(--border)] bg-[var(--surface-1)] p-[4px] shadow-sm transition-[left,bottom] duration-100 ease-out'
78+
className='-translate-x-1/2 fixed bottom-[calc(var(--terminal-height)+16px)] left-[calc((100vw+var(--sidebar-width)-var(--panel-width))/2)] z-10 flex h-[36px] items-center gap-[2px] rounded-[8px] border border-[var(--border)] bg-[var(--surface-1)] p-[4px] shadow-sm transition-[left,bottom] duration-100 ease-out'
7679
onContextMenu={handleContextMenu}
7780
>
78-
<Tooltip.Root>
79-
<Tooltip.Trigger asChild>
80-
<Button
81-
variant={mode === 'hand' ? 'secondary' : 'ghost'}
82-
className='h-[28px] w-[28px] p-0'
83-
onClick={() => setMode('hand')}
81+
{/* Canvas Mode Selector */}
82+
<Popover
83+
open={isCanvasModeOpen}
84+
onOpenChange={setIsCanvasModeOpen}
85+
variant='secondary'
86+
size='sm'
87+
>
88+
<PopoverTrigger asChild>
89+
<div className='flex cursor-pointer items-center gap-[4px]'>
90+
<Button className='h-[28px] w-[28px] rounded-[6px] p-0' variant='active'>
91+
{mode === 'hand' ? (
92+
<Hand className='h-[14px] w-[14px]' />
93+
) : (
94+
<Cursor className='h-[14px] w-[14px]' />
95+
)}
96+
</Button>
97+
<Button className='!p-[2px] group' variant='ghost'>
98+
<ChevronDown className='h-[8px] w-[10px] text-[var(--text-muted)] group-hover:text-[var(--text-secondary)]' />
99+
</Button>
100+
</div>
101+
</PopoverTrigger>
102+
<PopoverContent align='center' side='top' sideOffset={8} maxWidth={100} minWidth={100}>
103+
<PopoverItem
104+
onClick={() => {
105+
setMode('cursor')
106+
setIsCanvasModeOpen(false)
107+
}}
84108
>
85-
<Hand className='h-[16px] w-[16px]' />
86-
</Button>
87-
</Tooltip.Trigger>
88-
<Tooltip.Content side='top'>Hand tool</Tooltip.Content>
89-
</Tooltip.Root>
90-
91-
<Tooltip.Root>
92-
<Tooltip.Trigger asChild>
93-
<Button
94-
variant={mode === 'cursor' ? 'secondary' : 'ghost'}
95-
className='h-[28px] w-[28px] p-0'
96-
onClick={() => setMode('cursor')}
109+
<Cursor className='h-3 w-3' />
110+
<span>Pointer</span>
111+
</PopoverItem>
112+
<PopoverItem
113+
onClick={() => {
114+
setMode('hand')
115+
setIsCanvasModeOpen(false)
116+
}}
97117
>
98-
<Cursor className='h-[16px] w-[16px]' />
99-
</Button>
100-
</Tooltip.Trigger>
101-
<Tooltip.Content side='top'>Move</Tooltip.Content>
102-
</Tooltip.Root>
118+
<Hand className='h-3 w-3' />
119+
<span>Mover</span>
120+
</PopoverItem>
121+
</PopoverContent>
122+
</Popover>
103123

104124
<div className='mx-[4px] h-[20px] w-[1px] bg-[var(--border)]' />
105125

106126
<Tooltip.Root>
107127
<Tooltip.Trigger asChild>
108128
<Button
109129
variant='ghost'
110-
className='h-[28px] w-[28px] p-0'
130+
className='h-[28px] w-[28px] rounded-[6px] p-0 hover:bg-[var(--surface-5)]'
111131
onClick={undo}
112132
disabled={!canUndo}
113133
>
@@ -123,7 +143,7 @@ export function ActionBar() {
123143
<Tooltip.Trigger asChild>
124144
<Button
125145
variant='ghost'
126-
className='h-[28px] w-[28px] p-0'
146+
className='h-[28px] w-[28px] rounded-[6px] p-0 hover:bg-[var(--surface-5)]'
127147
onClick={redo}
128148
disabled={!canRedo}
129149
>
@@ -139,7 +159,11 @@ export function ActionBar() {
139159

140160
<Tooltip.Root>
141161
<Tooltip.Trigger asChild>
142-
<Button variant='ghost' className='h-[28px] w-[28px] p-0' onClick={() => zoomOut()}>
162+
<Button
163+
variant='ghost'
164+
className='h-[28px] w-[28px] rounded-[6px] p-0 hover:bg-[var(--surface-5)]'
165+
onClick={() => zoomOut()}
166+
>
143167
<ZoomOut className='h-[16px] w-[16px]' />
144168
</Button>
145169
</Tooltip.Trigger>
@@ -148,7 +172,11 @@ export function ActionBar() {
148172

149173
<Tooltip.Root>
150174
<Tooltip.Trigger asChild>
151-
<Button variant='ghost' className='h-[28px] w-[28px] p-0' onClick={() => zoomIn()}>
175+
<Button
176+
variant='ghost'
177+
className='h-[28px] w-[28px] rounded-[6px] p-0 hover:bg-[var(--surface-5)]'
178+
onClick={() => zoomIn()}
179+
>
152180
<ZoomIn className='h-[16px] w-[16px]' />
153181
</Button>
154182
</Tooltip.Trigger>
@@ -159,7 +187,7 @@ export function ActionBar() {
159187
<Tooltip.Trigger asChild>
160188
<Button
161189
variant='ghost'
162-
className='h-[28px] w-[28px] p-0'
190+
className='h-[28px] w-[28px] rounded-[6px] p-0 hover:bg-[var(--surface-5)]'
163191
onClick={() => fitViewToBounds({ padding: 0.1, duration: 300 })}
164192
>
165193
<Expand className='h-[16px] w-[16px]' />

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
PopoverItem,
2121
PopoverScrollArea,
2222
PopoverTrigger,
23+
Tooltip,
2324
Trash,
2425
} from '@/components/emcn'
2526
import { useSession } from '@/lib/auth/auth-client'
@@ -896,7 +897,7 @@ export function Chat() {
896897

897898
<div className='flex flex-shrink-0 items-center gap-[8px]'>
898899
{/* More menu with actions */}
899-
<Popover variant='default' open={moreMenuOpen} onOpenChange={setMoreMenuOpen}>
900+
<Popover variant='default' size='sm' open={moreMenuOpen} onOpenChange={setMoreMenuOpen}>
900901
<PopoverTrigger asChild>
901902
<Button
902903
variant='ghost'
@@ -1069,17 +1070,21 @@ export function Chat() {
10691070

10701071
{/* Buttons positioned absolutely on the right */}
10711072
<div className='-translate-y-1/2 absolute top-1/2 right-[2px] flex items-center gap-[10px]'>
1072-
<Badge
1073-
onClick={() => document.getElementById('floating-chat-file-input')?.click()}
1074-
title='Attach file'
1075-
className={cn(
1076-
'!bg-transparent !border-0 cursor-pointer rounded-[6px] p-[0px]',
1077-
(!activeWorkflowId || isExecuting || chatFiles.length >= 15) &&
1078-
'cursor-not-allowed opacity-50'
1079-
)}
1080-
>
1081-
<Paperclip className='!h-3.5 !w-3.5' />
1082-
</Badge>
1073+
<Tooltip.Root>
1074+
<Tooltip.Trigger asChild>
1075+
<Badge
1076+
onClick={() => document.getElementById('floating-chat-file-input')?.click()}
1077+
className={cn(
1078+
'!bg-transparent !border-0 cursor-pointer rounded-[6px] p-[0px]',
1079+
(!activeWorkflowId || isExecuting || chatFiles.length >= 15) &&
1080+
'cursor-not-allowed opacity-50'
1081+
)}
1082+
>
1083+
<Paperclip className='!h-3.5 !w-3.5' />
1084+
</Badge>
1085+
</Tooltip.Trigger>
1086+
<Tooltip.Content>Attach file</Tooltip.Content>
1087+
</Tooltip.Root>
10831088

10841089
{isStreaming ? (
10851090
<Button

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/context-menu/pane-context-menu.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export function PaneContextMenu({
2727
onToggleVariables,
2828
onToggleChat,
2929
onInvite,
30+
onZoomIn,
31+
onZoomOut,
32+
onFitView,
3033
isVariablesOpen = false,
3134
isChatOpen = false,
3235
hasClipboard = false,
@@ -113,6 +116,33 @@ export function PaneContextMenu({
113116
<span className='ml-auto opacity-70 group-hover:opacity-100'>⇧L</span>
114117
</PopoverItem>
115118

119+
{/* View actions */}
120+
<PopoverDivider />
121+
<PopoverItem
122+
onClick={() => {
123+
onZoomIn()
124+
onClose()
125+
}}
126+
>
127+
Zoom In
128+
</PopoverItem>
129+
<PopoverItem
130+
onClick={() => {
131+
onZoomOut()
132+
onClose()
133+
}}
134+
>
135+
Zoom Out
136+
</PopoverItem>
137+
<PopoverItem
138+
onClick={() => {
139+
onFitView()
140+
onClose()
141+
}}
142+
>
143+
Fit to View
144+
</PopoverItem>
145+
116146
{/* Navigation actions */}
117147
<PopoverDivider />
118148
<PopoverItem

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/context-menu/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export interface PaneContextMenuProps {
8080
onToggleVariables: () => void
8181
onToggleChat: () => void
8282
onInvite: () => void
83+
onZoomIn: () => void
84+
onZoomOut: () => void
85+
onFitView: () => void
8386
/** Whether the variables panel is currently open */
8487
isVariablesOpen?: boolean
8588
/** Whether the chat panel is currently open */

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,14 @@ export const Toolbar = forwardRef<ToolbarRef, ToolbarProps>(function Toolbar(
327327
/**
328328
* Handle search input blur.
329329
*
330-
* We intentionally keep search mode active after blur so that ArrowUp/Down
331-
* navigation continues to work after the first move from the search input
332-
* into the triggers/blocks list (e.g. when initiated via Mod+F).
330+
* If the search query is empty, deactivate search mode to show the search icon again.
331+
* If there's a query, keep search mode active so ArrowUp/Down navigation continues
332+
* to work after focus moves into the triggers/blocks list (e.g. when initiated via Mod+F).
333333
*/
334334
const handleSearchBlur = () => {
335-
// No-op by design
335+
if (!searchQuery.trim()) {
336+
setIsSearchActive(false)
337+
}
336338
}
337339

338340
/**

0 commit comments

Comments
 (0)