Files
resolutionflow/frontend/src/components/tree-editor/code-mode/CodeModeToolbar.tsx
Michael Chihlas d1a56f0529 refactor: migrate remaining components to Design System v4
111 files across 14 directories: common, tree-editor, kb-accelerator,
copilot, assistant, analytics, library, procedural, procedural-editor,
public, script-editor, ui, admin, step-library.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:18:15 -04:00

187 lines
5.7 KiB
TypeScript

import { ChevronDown, AlertCircle, CheckCircle2, Loader2, Plus, HelpCircle } from 'lucide-react'
import { useState, useRef, useEffect } from 'react'
import { cn } from '@/lib/utils'
import type { TreeMarkdownValidationError } from '@/types'
interface CodeModeToolbarProps {
validationErrors: TreeMarkdownValidationError[]
isValidating: boolean
isValid: boolean
onInsertTemplate: (template: string) => void
onToggleSyntaxHelp: () => void
syntaxHelpOpen: boolean
}
const NODE_TEMPLATES = {
decision: [
'',
'---',
'id: new_decision',
'type: decision',
'parent: root',
'---',
'# What is the question?',
'',
'> Help text for the engineer',
'',
'- [A] Option A → @target_id',
'- [B] Option B → @target_id',
'',
].join('\n'),
action: [
'',
'---',
'id: new_action',
'type: action',
'parent: root',
'---',
'## Action Title',
'',
'Description of what to do.',
'',
'```commands',
'command here',
'```',
'',
'**Expected:** Expected outcome',
'',
'→ @next_node_id',
'',
].join('\n'),
solution: [
'',
'---',
'id: new_solution',
'type: solution',
'parent: root',
'---',
'## Solution Title',
'',
'Description of the resolution.',
'',
'1. Step 1',
'2. Step 2',
'',
].join('\n'),
}
export function CodeModeToolbar({
validationErrors,
isValidating,
isValid,
onInsertTemplate,
onToggleSyntaxHelp,
syntaxHelpOpen,
}: CodeModeToolbarProps) {
const [insertOpen, setInsertOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
const errorCount = validationErrors.filter(e => e.severity === 'error').length
const warningCount = validationErrors.filter(e => e.severity === 'warning').length
// Close dropdown on outside click
useEffect(() => {
const handler = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setInsertOpen(false)
}
}
document.addEventListener('mousedown', handler)
return () => document.removeEventListener('mousedown', handler)
}, [])
return (
<div className="flex items-center justify-between border-b border-[#1e2130] bg-[#14161d] px-3 py-1.5">
<div className="flex items-center gap-2">
{/* Insert Node dropdown */}
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setInsertOpen(!insertOpen)}
className={cn(
'flex items-center gap-1 rounded-md border border-[#1e2130] px-2.5 py-1 text-xs font-medium text-[#848b9b]',
'hover:bg-accent hover:text-[#e2e5eb]'
)}
>
<Plus className="h-3 w-3" />
Insert Node
<ChevronDown className="h-3 w-3" />
</button>
{insertOpen && (
<div className="absolute left-0 top-full z-50 mt-1 w-44 rounded-lg border border-[#1e2130] bg-[#14161d] py-1 shadow-xl">
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.decision); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-[#848b9b] hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-blue-400" />
Decision
</button>
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.action); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-[#848b9b] hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-amber-400" />
Action
</button>
<button
onClick={() => { onInsertTemplate(NODE_TEMPLATES.solution); setInsertOpen(false) }}
className="flex w-full items-center gap-2 px-3 py-1.5 text-xs text-[#848b9b] hover:bg-accent"
>
<span className="h-2 w-2 rounded-full bg-emerald-400" />
Solution
</button>
</div>
)}
</div>
</div>
<div className="flex items-center gap-3">
{/* Validation status */}
<div className="flex items-center gap-1.5 text-xs">
{isValidating ? (
<>
<Loader2 className="h-3 w-3 animate-spin text-[#848b9b]" />
<span className="text-[#848b9b]">Validating...</span>
</>
) : isValid ? (
<>
<CheckCircle2 className="h-3 w-3 text-emerald-400" />
<span className="text-emerald-400/70">Synced</span>
{warningCount > 0 && (
<span className="text-yellow-400/70">
({warningCount} warning{warningCount !== 1 ? 's' : ''})
</span>
)}
</>
) : (
<>
<AlertCircle className="h-3 w-3 text-red-400" />
<span className="text-red-400/70">
{errorCount} error{errorCount !== 1 ? 's' : ''}
</span>
{warningCount > 0 && (
<span className="text-yellow-400/70">
, {warningCount} warning{warningCount !== 1 ? 's' : ''}
</span>
)}
</>
)}
</div>
{/* Syntax Help toggle */}
<button
onClick={onToggleSyntaxHelp}
className={cn(
'flex items-center gap-1 rounded-md px-2 py-1 text-xs',
syntaxHelpOpen
? 'bg-accent text-[#e2e5eb]'
: 'text-[#848b9b] hover:bg-accent hover:text-[#848b9b]'
)}
>
<HelpCircle className="h-3 w-3" />
Syntax
</button>
</div>
</div>
)
}