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>
122 lines
4.6 KiB
TypeScript
122 lines
4.6 KiB
TypeScript
import { useState, useRef, useEffect } from 'react'
|
|
import { HelpCircle, Zap, CheckCircle, Trash2 } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import type { TreeStructure } from '@/types'
|
|
|
|
interface AnswerStubCardProps {
|
|
node: TreeStructure // type === 'answer'
|
|
fromOption?: string
|
|
onSelectType: (nodeId: string, type: 'decision' | 'action' | 'solution') => void
|
|
onDelete: (nodeId: string) => void
|
|
}
|
|
|
|
export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: AnswerStubCardProps) {
|
|
const [picking, setPicking] = useState(false)
|
|
const [confirming, setConfirming] = useState(false)
|
|
const cardRef = useRef<HTMLDivElement>(null)
|
|
const label = fromOption || node.title || 'Answer'
|
|
|
|
// Collapse picker when clicking outside the card
|
|
useEffect(() => {
|
|
if (!picking) return
|
|
const handleOutsideClick = (e: MouseEvent) => {
|
|
if (cardRef.current && !cardRef.current.contains(e.target as Node)) {
|
|
setPicking(false)
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleOutsideClick)
|
|
return () => document.removeEventListener('mousedown', handleOutsideClick)
|
|
}, [picking])
|
|
|
|
return (
|
|
<div
|
|
ref={cardRef}
|
|
className={cn(
|
|
'relative min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-[#1e2130] bg-[#14161d]/50',
|
|
'transition-all duration-150',
|
|
!picking && !confirming && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30'
|
|
)}
|
|
onClick={() => !picking && !confirming && setPicking(true)}
|
|
>
|
|
{/* Delete button — top-right corner */}
|
|
{!picking && !confirming && (
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); setConfirming(true) }}
|
|
className="absolute top-1.5 right-1.5 rounded p-0.5 text-[#848b9b]/40 hover:bg-red-500/10 hover:text-red-400 transition-colors"
|
|
title="Delete stub"
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
</button>
|
|
)}
|
|
|
|
{/* Label */}
|
|
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-[#e2e5eb] text-center">
|
|
{label}
|
|
</div>
|
|
|
|
{/* Confirm delete */}
|
|
{confirming ? (
|
|
<div className="px-2 pb-2.5 text-center space-y-1.5">
|
|
<p className="text-[10px] text-[#848b9b]">Delete this stub?</p>
|
|
<div className="flex items-center justify-center gap-1.5">
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); onDelete(node.id) }}
|
|
className="rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-red-500/30 bg-red-500/10 text-red-400 hover:bg-red-500/20"
|
|
>
|
|
Delete
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); setConfirming(false) }}
|
|
className="rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-[#1e2130] text-[#848b9b] hover:bg-accent"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : !picking ? (
|
|
<div className="pb-2.5 text-center text-[10px] text-[#848b9b] font-sans text-xs">
|
|
+ Choose Type
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center justify-center gap-1.5 pb-2.5 px-2">
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
|
className={cn(
|
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
|
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
|
)}
|
|
>
|
|
<HelpCircle className="h-2.5 w-2.5" /> Decision
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'action') }}
|
|
className={cn(
|
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
|
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
|
)}
|
|
>
|
|
<Zap className="h-2.5 w-2.5" /> Action
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'solution') }}
|
|
className={cn(
|
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
|
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
|
)}
|
|
>
|
|
<CheckCircle className="h-2.5 w-2.5" /> Solution
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default AnswerStubCard
|