diff --git a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx index f7bbcd00946..2d8747efb92 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx @@ -57,14 +57,26 @@ function parseShortcut(shortcut: string): ParsedShortcut { } } +/** + * Maps a KeyboardEvent.code value to the logical key name used in shortcut definitions. + * Needed for international keyboard layouts where e.key may produce unexpected characters + * (e.g. macOS Option+letter yields 'å' instead of 'a', dead keys yield 'Dead'). + */ +function codeToKey(code: string): string | undefined { + if (code.startsWith('Key')) return code.slice(3).toLowerCase() + if (code.startsWith('Digit')) return code.slice(5) + return undefined +} + function matchesShortcut(e: KeyboardEvent, parsed: ParsedShortcut): boolean { const isMac = isMacPlatform() const expectedCtrl = parsed.ctrl || (parsed.mod ? !isMac : false) const expectedMeta = parsed.meta || (parsed.mod ? isMac : false) const eventKey = e.key.length === 1 ? e.key.toLowerCase() : e.key + const keyMatches = eventKey === parsed.key || codeToKey(e.code) === parsed.key return ( - eventKey === parsed.key && + keyMatches && !!e.ctrlKey === !!expectedCtrl && !!e.metaKey === !!expectedMeta && !!e.shiftKey === !!parsed.shift && diff --git a/apps/sim/app/workspace/[workspaceId]/utils/commands-utils.ts b/apps/sim/app/workspace/[workspaceId]/utils/commands-utils.ts index fd9e9678287..8bdbeac2577 100644 --- a/apps/sim/app/workspace/[workspaceId]/utils/commands-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/utils/commands-utils.ts @@ -9,6 +9,8 @@ import type { GlobalCommand } from '@/app/workspace/[workspaceId]/providers/glob export type CommandId = | 'accept-diff-changes' | 'add-agent' + | 'add-workflow' + | 'add-task' // | 'goto-templates' | 'goto-logs' | 'open-search' @@ -52,6 +54,16 @@ export const COMMAND_DEFINITIONS: Record = { shortcut: 'Mod+Shift+A', allowInEditable: true, }, + 'add-workflow': { + id: 'add-workflow', + shortcut: 'Mod+Shift+P', + allowInEditable: false, + }, + 'add-task': { + id: 'add-task', + shortcut: 'Mod+Shift+K', + allowInEditable: false, + }, // 'goto-templates': { // id: 'goto-templates', // shortcut: 'Mod+Y', diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx index 592d3562d6a..23c34bfdea4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx @@ -343,7 +343,7 @@ export function SearchModal({ '-translate-x-1/2 fixed top-[15%] z-50 w-[500px] rounded-xl border-[4px] border-black/[0.06] bg-[var(--bg)] shadow-[0_24px_80px_-16px_rgba(0,0,0,0.15)] dark:border-white/[0.06] dark:shadow-[0_24px_80px_-16px_rgba(0,0,0,0.4)]', open ? 'visible opacity-100' : 'invisible opacity-0' )} - style={{ left: '50%' }} + style={{ left: 'calc(var(--sidebar-width) / 2 + 50%)' }} >
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx index a48982e5631..deac68e7c99 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx @@ -37,6 +37,7 @@ import { } from '@/components/emcn/icons' import { useSession } from '@/lib/auth/auth-client' import { cn } from '@/lib/core/utils/cn' +import { isMacPlatform } from '@/lib/core/utils/platform' import { START_NAV_TOUR_EVENT, START_WORKFLOW_TOUR_EVENT, @@ -322,6 +323,8 @@ export const Sidebar = memo(function Sidebar() { isCollapsedRef.current = isCollapsed }, [isCollapsed]) + const isMac = useMemo(() => isMacPlatform(), []) + // Delay collapsed tooltips until the width transition finishes. const [showCollapsedTooltips, setShowCollapsedTooltips] = useState(isCollapsed) @@ -1065,6 +1068,16 @@ export const Sidebar = memo(function Sidebar() { // Stable callback for DeleteModal close const handleCloseTaskDeleteModal = useCallback(() => setIsTaskDeleteModalOpen(false), []) + const handleEdgeKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (isCollapsed && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault() + toggleCollapsed() + } + }, + [isCollapsed, toggleCollapsed] + ) + // Stable handler for help modal open from dropdown const handleOpenHelpFromMenu = useCallback(() => setIsHelpModalOpen(true), []) @@ -1153,548 +1166,578 @@ export const Sidebar = memo(function Sidebar() { openSearchModal() }, }, + { + id: 'add-workflow', + handler: () => { + if (!canEdit || isCreatingWorkflow) return + handleCreateWorkflow() + }, + }, + { + id: 'add-task', + handler: () => { + handleNewTask() + }, + }, ]) ) return ( <> - +
{/* Universal Search Modal */}