From c8e67515b1eaaa8a639e4e79dee91ef60b9a4741 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sun, 15 Feb 2026 05:07:16 -0500 Subject: [PATCH] feat: add Quick Launch modal with actions and recent flows - Zap button opens Quick Launch with create/navigate shortcuts - Shows recent flows for quick session start - Keyboard navigation support (arrows + enter) Co-Authored-By: Claude Opus 4.6 --- .../src/components/layout/QuickLaunch.tsx | 157 ++++++++++++++++++ frontend/src/components/layout/TopBar.tsx | 9 +- 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/layout/QuickLaunch.tsx diff --git a/frontend/src/components/layout/QuickLaunch.tsx b/frontend/src/components/layout/QuickLaunch.tsx new file mode 100644 index 00000000..c03aab66 --- /dev/null +++ b/frontend/src/components/layout/QuickLaunch.tsx @@ -0,0 +1,157 @@ +import { useState, useEffect, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { Plus, Play, FileText, Bookmark, Users, X } from 'lucide-react' +import { treesApi } from '@/api/trees' +import type { TreeListItem } from '@/types' +import { getTreeNavigatePath } from '@/lib/routing' +import { cn } from '@/lib/utils' + +interface QuickLaunchProps { + open: boolean + onClose: () => void +} + +interface QuickAction { + id: string + icon: typeof Plus + label: string + description: string + path: string + color: string +} + +const ACTIONS: QuickAction[] = [ + { id: 'new-tree', icon: Plus, label: 'New Troubleshooting Flow', description: 'Create a branching decision tree', path: '/trees/new', color: '#3b82f6' }, + { id: 'new-procedure', icon: Plus, label: 'New Procedure', description: 'Create a step-by-step procedure', path: '/flows/new', color: '#8b5cf6' }, + { id: 'sessions', icon: Play, label: 'View Sessions', description: 'See active and recent sessions', path: '/sessions', color: '#f59e0b' }, + { id: 'step-library', icon: Bookmark, label: 'Step Library', description: 'Browse reusable steps', path: '/step-library', color: '#10b981' }, + { id: 'exports', icon: FileText, label: 'Exports & Shares', description: 'View shared session exports', path: '/shares', color: '#6366f1' }, + { id: 'team', icon: Users, label: 'Team Settings', description: 'Manage team members and roles', path: '/account', color: '#ec4899' }, +] + +export function QuickLaunch({ open, onClose }: QuickLaunchProps) { + const navigate = useNavigate() + const [recentTrees, setRecentTrees] = useState([]) + const [selectedIndex, setSelectedIndex] = useState(0) + const containerRef = useRef(null) + + const allItems = [...ACTIONS.map(a => ({ type: 'action' as const, ...a })), ...recentTrees.slice(0, 4).map(t => ({ type: 'tree' as const, ...t }))] + const totalItems = allItems.length + + useEffect(() => { + if (open) { + setSelectedIndex(0) + treesApi.list({ sort_by: 'updated_at' }) + .then(trees => setRecentTrees(trees.slice(0, 4))) + .catch(() => {}) + } + }, [open]) + + useEffect(() => { + if (!open) return + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex(i => Math.min(i + 1, totalItems - 1)) } + if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex(i => Math.max(i - 1, 0)) } + if (e.key === 'Enter') { + e.preventDefault() + const item = allItems[selectedIndex] + if (!item) return + onClose() + if (item.type === 'action') navigate(item.path) + else navigate(getTreeNavigatePath(item.id, item.tree_type)) + } + } + document.addEventListener('keydown', handler) + return () => document.removeEventListener('keydown', handler) + }, [open, selectedIndex, totalItems, allItems, navigate, onClose]) + + if (!open) return null + + return ( +
+
+
+
+

Quick Launch

+ +
+ +
+ {/* Actions */} +

Actions

+ {ACTIONS.map((action, i) => { + const Icon = action.icon + return ( + + ) + })} + + {/* Recent flows */} + {recentTrees.length > 0 && ( + <> +

Recent Flows

+ {recentTrees.slice(0, 4).map((tree, ti) => { + const idx = ACTIONS.length + ti + return ( + + ) + })} + + )} +
+ +
+ + โ†‘โ†“ + Navigate + + + โ†ต + Open + +
+
+
+ ) +} diff --git a/frontend/src/components/layout/TopBar.tsx b/frontend/src/components/layout/TopBar.tsx index 1529d725..0cec8634 100644 --- a/frontend/src/components/layout/TopBar.tsx +++ b/frontend/src/components/layout/TopBar.tsx @@ -7,6 +7,7 @@ import { useWorkspaceStore } from '@/store/workspaceStore' import { getWorkspaceLabels } from '@/constants/workspaceLabels' import { BrandLogo } from '@/components/common/BrandLogo' import { CommandPalette } from './CommandPalette' +import { QuickLaunch } from './QuickLaunch' import { cn } from '@/lib/utils' export function TopBar() { @@ -19,6 +20,7 @@ export function TopBar() { const [userMenuOpen, setUserMenuOpen] = useState(false) const [commandPaletteOpen, setCommandPaletteOpen] = useState(false) + const [quickLaunchOpen, setQuickLaunchOpen] = useState(false) const menuRef = useRef(null) const handleLogout = async () => { @@ -94,7 +96,11 @@ export function TopBar() { {/* Action buttons */}
-