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>
This commit is contained in:
@@ -40,19 +40,19 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
const allHandled = pendingFixes.length === 0
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
||||
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-[#14161d] border border-[#1e2130] rounded-2xl shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
<Sparkles className="h-5 w-5 text-[#22d3ee]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||
AI Fix Proposals ({fixes.length})
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -73,16 +73,16 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
isApplied
|
||||
? 'border-emerald-400/30 bg-emerald-400/5'
|
||||
: isSkipped
|
||||
? 'border-border bg-accent/30 opacity-60'
|
||||
: 'border-border bg-card'
|
||||
? 'border-[#1e2130] bg-accent/30 opacity-60'
|
||||
: 'border-[#1e2130] bg-[#14161d]'
|
||||
)}
|
||||
>
|
||||
{/* Fix header */}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-red-400 mb-1">{fix.error_message}</p>
|
||||
<p className="text-sm text-foreground">{fix.description}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-sm text-[#e2e5eb]">{fix.description}</p>
|
||||
<p className="text-xs text-[#848b9b] mt-1">
|
||||
Node: {fix.target_node_id}
|
||||
</p>
|
||||
</div>
|
||||
@@ -92,7 +92,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
</span>
|
||||
)}
|
||||
{isSkipped && (
|
||||
<span className="text-xs text-muted-foreground">Skipped</span>
|
||||
<span className="text-xs text-[#848b9b]">Skipped</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
<>
|
||||
<button
|
||||
onClick={() => toggleExpanded(fix.target_node_id)}
|
||||
className="mt-2 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
||||
className="mt-2 flex items-center gap-1 text-xs text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
{isExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
||||
{isExpanded ? 'Hide' : 'Show'} details
|
||||
@@ -110,14 +110,14 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
{isExpanded && (
|
||||
<div className="mt-3 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1">Before</p>
|
||||
<pre className="overflow-x-auto rounded bg-accent/50 p-2 text-xs text-muted-foreground max-h-48 overflow-y-auto">
|
||||
<p className="text-xs font-medium text-[#848b9b] mb-1">Before</p>
|
||||
<pre className="overflow-x-auto rounded bg-accent/50 p-2 text-xs text-[#848b9b] max-h-48 overflow-y-auto">
|
||||
{JSON.stringify(fix.original_node, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-medium text-emerald-400 mb-1">After</p>
|
||||
<pre className="overflow-x-auto rounded bg-emerald-400/5 p-2 text-xs text-foreground max-h-48 overflow-y-auto">
|
||||
<pre className="overflow-x-auto rounded bg-emerald-400/5 p-2 text-xs text-[#e2e5eb] max-h-48 overflow-y-auto">
|
||||
{JSON.stringify(fix.fixed_node, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between border-t border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between border-t border-[#1e2130] px-6 py-4">
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
{allHandled ? 'Done' : 'Cancel'}
|
||||
</Button>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={cn(
|
||||
'relative min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50',
|
||||
'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'
|
||||
)}
|
||||
@@ -43,7 +43,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); setConfirming(true) }}
|
||||
className="absolute top-1.5 right-1.5 rounded p-0.5 text-muted-foreground/40 hover:bg-red-500/10 hover:text-red-400 transition-colors"
|
||||
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" />
|
||||
@@ -51,33 +51,33 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
)}
|
||||
|
||||
{/* Label */}
|
||||
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-foreground text-center">
|
||||
<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-muted-foreground">Delete this stub?</p>
|
||||
<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-label border border-red-500/30 bg-red-500/10 text-red-400 hover:bg-red-500/20"
|
||||
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-label border border-border text-muted-foreground hover:bg-accent"
|
||||
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-muted-foreground font-label">
|
||||
<div className="pb-2.5 text-center text-[10px] text-[#848b9b] font-sans text-xs">
|
||||
+ Choose Type
|
||||
</div>
|
||||
) : (
|
||||
@@ -86,7 +86,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
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-label',
|
||||
'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'
|
||||
)}
|
||||
>
|
||||
@@ -96,7 +96,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
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-label',
|
||||
'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'
|
||||
)}
|
||||
>
|
||||
@@ -106,7 +106,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
||||
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-label',
|
||||
'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'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -51,7 +51,7 @@ export function DynamicArrayField<T>({
|
||||
type="button"
|
||||
onClick={() => handleMoveUp(index)}
|
||||
disabled={index === 0}
|
||||
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
|
||||
className="rounded p-0.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-30"
|
||||
title="Move up"
|
||||
aria-label="Move up"
|
||||
>
|
||||
@@ -61,7 +61,7 @@ export function DynamicArrayField<T>({
|
||||
type="button"
|
||||
onClick={() => handleMoveDown(index)}
|
||||
disabled={index === items.length - 1}
|
||||
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
|
||||
className="rounded p-0.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-30"
|
||||
title="Move down"
|
||||
aria-label="Move down"
|
||||
>
|
||||
@@ -78,7 +78,7 @@ export function DynamicArrayField<T>({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRemove(index)}
|
||||
className="mt-1 rounded p-1 text-muted-foreground hover:bg-red-400/20 hover:text-red-400"
|
||||
className="mt-1 rounded p-1 text-[#848b9b] hover:bg-red-400/20 hover:text-red-400"
|
||||
title="Remove"
|
||||
aria-label="Remove"
|
||||
>
|
||||
@@ -94,9 +94,9 @@ export function DynamicArrayField<T>({
|
||||
type="button"
|
||||
onClick={onAdd}
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border',
|
||||
'px-3 py-2 text-sm text-muted-foreground',
|
||||
'hover:border-border hover:text-foreground'
|
||||
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-[#1e2130]',
|
||||
'px-3 py-2 text-sm text-[#848b9b]',
|
||||
'hover:border-[#1e2130] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
@@ -106,7 +106,7 @@ export function DynamicArrayField<T>({
|
||||
|
||||
{/* Empty state */}
|
||||
{items.length === 0 && !canAdd && (
|
||||
<p className="text-center text-sm text-muted-foreground">No items</p>
|
||||
<p className="text-center text-sm text-[#848b9b]">No items</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -147,13 +147,13 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
||||
>
|
||||
<GlowEdgeDefs />
|
||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
|
||||
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
|
||||
<Controls showInteractive={false} className="bg-[#14161d]! border-[#1e2130]! shadow-lg!" />
|
||||
{minimapVisible && (
|
||||
<MiniMap
|
||||
pannable
|
||||
zoomable
|
||||
nodeColor={minimapNodeColor}
|
||||
className="bg-card! border-border!"
|
||||
className="bg-[#14161d]! border-[#1e2130]!"
|
||||
nodeStrokeWidth={2}
|
||||
/>
|
||||
)}
|
||||
@@ -163,7 +163,7 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
||||
<button
|
||||
onClick={() => setMinimapVisible(v => !v)}
|
||||
className={cn(
|
||||
'absolute bottom-2 right-2 z-10 rounded-lg border border-border bg-card p-2 text-muted-foreground shadow-lg hover:bg-accent hover:text-foreground transition-colors',
|
||||
'absolute bottom-2 right-2 z-10 rounded-lg border border-[#1e2130] bg-[#14161d] p-2 text-[#848b9b] shadow-lg hover:bg-accent hover:text-[#e2e5eb] transition-colors',
|
||||
minimapVisible && 'bottom-[170px]'
|
||||
)}
|
||||
title={minimapVisible ? 'Hide minimap' : 'Show minimap'}
|
||||
|
||||
@@ -20,18 +20,18 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50 transition-all',
|
||||
'w-[280px] rounded-xl border-2 border-dashed border-[#1e2130] bg-[#14161d]/50 transition-all',
|
||||
!picking && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30',
|
||||
selected && 'ring-1 ring-primary'
|
||||
)}
|
||||
onClick={() => !picking && setPicking(true)}
|
||||
>
|
||||
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-foreground text-center">
|
||||
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-[#e2e5eb] text-center">
|
||||
{label}
|
||||
</div>
|
||||
|
||||
{!picking ? (
|
||||
<div className="pb-2.5 text-center text-[10px] text-muted-foreground font-label">
|
||||
<div className="pb-2.5 text-center text-[10px] text-[#848b9b] font-sans text-xs">
|
||||
+ Choose Type
|
||||
</div>
|
||||
) : (
|
||||
@@ -39,21 +39,21 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
||||
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20"
|
||||
className="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="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
|
||||
className="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="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20"
|
||||
className="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>
|
||||
|
||||
@@ -69,7 +69,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
<div
|
||||
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
||||
className={cn(
|
||||
'group w-[280px] rounded-xl border border-border bg-card shadow-xs cursor-pointer transition-all',
|
||||
'group w-[280px] rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xs cursor-pointer transition-all',
|
||||
config.borderClass,
|
||||
selected && 'ring-1 ring-primary shadow-md',
|
||||
isGhost && 'border-dashed border-primary/40! opacity-60'
|
||||
@@ -83,13 +83,13 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
</span>
|
||||
|
||||
{/* Title */}
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">
|
||||
{title}
|
||||
</span>
|
||||
|
||||
{/* Badges */}
|
||||
{isNew && (
|
||||
<span className="rounded-full bg-yellow-500/20 px-1.5 py-0.5 text-[10px] font-label text-yellow-400">
|
||||
<span className="rounded-full bg-yellow-500/20 px-1.5 py-0.5 text-[10px] font-sans text-xs text-yellow-400">
|
||||
New
|
||||
</span>
|
||||
)}
|
||||
@@ -105,7 +105,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
e.stopPropagation()
|
||||
onDelete(node.id)
|
||||
}}
|
||||
className="shrink-0 rounded-md p-1 text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-red-500/20 hover:text-red-400 transition-all"
|
||||
className="shrink-0 rounded-md p-1 text-[#848b9b] opacity-0 group-hover:opacity-100 hover:bg-red-500/20 hover:text-red-400 transition-all"
|
||||
title="Delete node"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
@@ -115,19 +115,19 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
|
||||
{/* Decision options preview */}
|
||||
{node.type === 'decision' && optionCount > 0 && (
|
||||
<div className="border-t border-border px-3 py-1.5">
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span className="font-label">{optionCount} option{optionCount !== 1 ? 's' : ''}</span>
|
||||
<div className="border-t border-[#1e2130] px-3 py-1.5">
|
||||
<div className="flex items-center gap-1.5 text-xs text-[#848b9b]">
|
||||
<span className="font-sans text-xs">{optionCount} option{optionCount !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div className="mt-1 space-y-0.5">
|
||||
{node.options!.slice(0, 3).map((opt, i) => (
|
||||
<div key={opt.id} className="truncate text-xs text-muted-foreground">
|
||||
<span className="font-label text-foreground/60">{String.fromCharCode(65 + i)}</span>{' '}
|
||||
<div key={opt.id} className="truncate text-xs text-[#848b9b]">
|
||||
<span className="font-sans text-xs text-[#e2e5eb]/60">{String.fromCharCode(65 + i)}</span>{' '}
|
||||
{opt.label || '(empty)'}
|
||||
</div>
|
||||
))}
|
||||
{optionCount > 3 && (
|
||||
<div className="text-xs text-muted-foreground">+{optionCount - 3} more</div>
|
||||
<div className="text-xs text-[#848b9b]">+{optionCount - 3} more</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,31 +135,31 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
|
||||
{/* Description preview for action/solution */}
|
||||
{(node.type === 'action' || node.type === 'solution') && node.description && (
|
||||
<div className="border-t border-border px-3 py-1.5">
|
||||
<div className="line-clamp-2 text-xs text-muted-foreground">{node.description}</div>
|
||||
<div className="border-t border-[#1e2130] px-3 py-1.5">
|
||||
<div className="line-clamp-2 text-xs text-[#848b9b]">{node.description}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Collapse chevron */}
|
||||
{hasChildren && (
|
||||
<div className="flex justify-center border-t border-border py-1">
|
||||
<div className="flex justify-center border-t border-[#1e2130] py-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onToggleCollapse(node.id)
|
||||
}}
|
||||
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
<span className="font-label">Expand</span>
|
||||
<span className="font-sans text-xs">Expand</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
<span className="font-label">Collapse</span>
|
||||
<span className="font-sans text-xs">Collapse</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -168,7 +168,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
||||
|
||||
{/* Ghost node accept/dismiss overlay */}
|
||||
{isGhost && (
|
||||
<div className="mt-2 flex gap-2 border-t border-border/50 pt-2 px-3 pb-2">
|
||||
<div className="mt-2 flex gap-2 border-t border-[#1e2130]/50 pt-2 px-3 pb-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -28,26 +28,26 @@ export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
|
||||
<>
|
||||
{/* Backdrop — click to close */}
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-xs"
|
||||
className="fixed inset-0 z-40 bg-[#0c0d10]/40"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Side panel — slides in from right */}
|
||||
<div
|
||||
className="fixed right-0 top-0 z-50 flex h-full w-80 flex-col border-l border-border bg-card shadow-xl"
|
||||
className="fixed right-0 top-0 z-50 flex h-full w-80 flex-col border-l border-[#1e2130] bg-[#14161d] shadow-xl"
|
||||
role="dialog"
|
||||
aria-label="Flow metadata"
|
||||
>
|
||||
{/* Panel header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
||||
<h2 className="text-sm font-semibold text-foreground font-heading">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-4 py-3">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] font-heading">
|
||||
Flow Details
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
aria-label="Close metadata panel"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
||||
@@ -85,7 +85,7 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
|
||||
return (
|
||||
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent} allowFullScreen={true}>
|
||||
{/* Node ID display */}
|
||||
<div className="mb-4 text-xs text-muted-foreground">
|
||||
<div className="mb-4 text-xs text-[#848b9b]">
|
||||
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -135,17 +135,17 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
// Answer stub: show type picker instead of form
|
||||
if (node.type === 'answer') {
|
||||
return (
|
||||
<div ref={panelRef} className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
||||
<span className="text-sm font-heading font-medium text-foreground">
|
||||
<div ref={panelRef} className="flex h-full w-[400px] shrink-0 flex-col border-l border-[#1e2130] bg-[#14161d]">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-4 py-3">
|
||||
<span className="text-sm font-heading font-medium text-[#e2e5eb]">
|
||||
{node.title || 'Answer Placeholder'}
|
||||
</span>
|
||||
<button onClick={handleClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground">
|
||||
<button onClick={handleClose} className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-4">
|
||||
<p className="text-sm text-muted-foreground text-center">Choose a type for this node:</p>
|
||||
<p className="text-sm text-[#848b9b] text-center">Choose a type for this node:</p>
|
||||
<div className="flex gap-2">
|
||||
{(['decision', 'action', 'solution'] as const).map(type => {
|
||||
const cfg = TYPE_CONFIG[type]
|
||||
@@ -156,7 +156,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
type="button"
|
||||
onClick={() => onSelectType?.(nodeId, type)}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-label border transition-colors',
|
||||
'flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-sans text-xs border transition-colors',
|
||||
type === 'decision' && 'border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20',
|
||||
type === 'action' && 'border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20',
|
||||
type === 'solution' && 'border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20',
|
||||
@@ -178,14 +178,14 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
const isRoot = treeStructure?.id === nodeId
|
||||
|
||||
return (
|
||||
<div ref={panelRef} className="flex h-full min-h-0 w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
||||
<div ref={panelRef} className="flex h-full min-h-0 w-[400px] shrink-0 flex-col border-l border-[#1e2130] bg-[#14161d]">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2 border-b border-border px-4 py-3 shrink-0">
|
||||
<div className="flex items-center gap-2 border-b border-[#1e2130] px-4 py-3 shrink-0">
|
||||
<span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded', config.badgeClass)}>
|
||||
<TypeIcon className="h-3 w-3" />
|
||||
</span>
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">{title}</span>
|
||||
<button onClick={handleClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground">
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">{title}</span>
|
||||
<button onClick={handleClose} className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -198,20 +198,20 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="sticky bottom-0 flex items-center gap-2 border-t border-border bg-card px-4 py-3 shrink-0">
|
||||
<div className="sticky bottom-0 flex items-center gap-2 border-t border-[#1e2130] bg-[#14161d] px-4 py-3 shrink-0">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={!isDirty}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 transition-opacity',
|
||||
isDirty ? 'bg-gradient-brand hover:opacity-90' : 'bg-gradient-brand opacity-50 cursor-not-allowed'
|
||||
'flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity',
|
||||
isDirty ? 'bg-[#22d3ee] hover:brightness-110' : 'bg-[#22d3ee] opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<Save className="h-3.5 w-3.5" /> Save
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="rounded-lg border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg border border-[#1e2130] px-4 py-2 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -220,7 +220,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
<>
|
||||
<button
|
||||
onClick={handleDuplicate}
|
||||
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
title="Duplicate"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
@@ -235,7 +235,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
className="rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
|
||||
className="rounded-md px-2 py-1 text-xs text-[#848b9b] hover:bg-accent"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -243,7 +243,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-red-400"
|
||||
className="rounded-md p-2 text-[#848b9b] hover:bg-accent hover:text-red-400"
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
|
||||
@@ -51,7 +51,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
<div className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Title <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -61,9 +61,9 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
placeholder="e.g., Restart the Service"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
titleError ? 'border-red-400' : 'border-border'
|
||||
titleError ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
/>
|
||||
{titleError && (
|
||||
@@ -74,7 +74,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
{/* Description */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
Description
|
||||
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
||||
</label>
|
||||
@@ -82,14 +82,14 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPreview(!showPreview)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
||||
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
{showPreview ? 'Edit' : 'Preview'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{showPreview && node.description ? (
|
||||
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
|
||||
<div className="mt-1 rounded-md border border-[#1e2130] bg-accent/50 p-3 text-sm">
|
||||
<MarkdownContent content={node.description} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -105,8 +105,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
**Note:** Important information here"
|
||||
rows={5}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
@@ -115,7 +115,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
|
||||
{/* Commands */}
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
Commands
|
||||
<InfoTip text="PowerShell or CLI commands to execute" />
|
||||
</label>
|
||||
@@ -132,8 +132,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
onChange={(e) => handleUpdateCommand(index, e.target.value)}
|
||||
placeholder="e.g., Get-Service BrokerAgent"
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-md border border-[#1e2130] px-3 py-2 font-mono text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
@@ -143,7 +143,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
|
||||
{/* Expected Outcome */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Expected Outcome
|
||||
</label>
|
||||
<input
|
||||
@@ -152,8 +152,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
|
||||
placeholder="e.g., Service should show as Running"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
@@ -161,13 +161,13 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
|
||||
{/* Link to existing node */}
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
<Link2 className="h-3.5 w-3.5" />
|
||||
Next Step
|
||||
</label>
|
||||
{hasNextNode ? (
|
||||
<div className="mt-1 flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2">
|
||||
<span className="flex-1 truncate text-sm text-foreground">
|
||||
<span className="flex-1 truncate text-sm text-[#e2e5eb]">
|
||||
Linked to: {(() => {
|
||||
const treeStructure = useTreeEditorStore.getState().treeStructure
|
||||
const allNodes = collectAllNodesFlat(treeStructure)
|
||||
@@ -178,7 +178,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdate({ next_node_id: undefined })}
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
title="Remove link"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
@@ -193,8 +193,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#14161d] text-[#e2e5eb]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -212,7 +212,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
})()}
|
||||
</select>
|
||||
)}
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
{hasNextNode
|
||||
? 'This action will navigate to the linked node.'
|
||||
: 'Select a node to navigate to after this action, or save to create a new placeholder.'}
|
||||
|
||||
@@ -91,7 +91,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
|
||||
{/* Question */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -103,9 +103,9 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
: "e.g., Can you ping the server?"}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
questionError ? 'border-red-400' : 'border-border'
|
||||
questionError ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
/>
|
||||
{questionError && (
|
||||
@@ -115,7 +115,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
|
||||
{/* Help Text */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Help Text
|
||||
</label>
|
||||
<textarea
|
||||
@@ -124,8 +124,8 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
placeholder="Additional context or instructions for this decision..."
|
||||
rows={2}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
@@ -133,13 +133,13 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
|
||||
{/* Options */}
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
|
||||
<InfoTip text={isRootNode
|
||||
? "Add as many options as needed (A, B, C, D...). Each option leads to a different troubleshooting path."
|
||||
: "Each option can branch to a different next step."} />
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground mt-1">Options become answer placeholders you can fill in later.</p>
|
||||
<p className="text-xs text-[#848b9b] mt-1">Options become answer placeholders you can fill in later.</p>
|
||||
{optionsError && (
|
||||
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
|
||||
)}
|
||||
@@ -162,7 +162,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn(
|
||||
'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold',
|
||||
isRootNode ? 'bg-blue-500/20 text-blue-400' : 'bg-accent text-muted-foreground'
|
||||
isRootNode ? 'bg-blue-500/20 text-blue-400' : 'bg-accent text-[#848b9b]'
|
||||
)}>
|
||||
{letter}
|
||||
</span>
|
||||
@@ -186,9 +186,9 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
}}
|
||||
className={cn(
|
||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
|
||||
optionLabelError ? 'border-red-400' : 'border-border'
|
||||
optionLabelError ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
/>
|
||||
{optionLabelError && (
|
||||
@@ -205,7 +205,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
const target = allNodes.find(n => n.id === option.next_node_id)
|
||||
if (!target) return null
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-xs text-primary" title={`Links to: ${target.label}`}>
|
||||
<div className="flex items-center gap-1 text-xs text-[#22d3ee]" title={`Links to: ${target.label}`}>
|
||||
<Link2 className="h-3 w-3" />
|
||||
</div>
|
||||
)
|
||||
@@ -218,20 +218,20 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
|
||||
{/* Quick-link: assign an option to an existing node */}
|
||||
<details className="mt-2">
|
||||
<summary className="cursor-pointer text-xs text-muted-foreground hover:text-foreground">
|
||||
<summary className="cursor-pointer text-xs text-[#848b9b] hover:text-[#e2e5eb]">
|
||||
<Link2 className="inline h-3 w-3 mr-1" />
|
||||
Link an option to an existing node (cross-reference)
|
||||
</summary>
|
||||
<div className="mt-2 space-y-2 rounded-md border border-border bg-accent/30 p-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<div className="mt-2 space-y-2 rounded-md border border-[#1e2130] bg-accent/30 p-3">
|
||||
<p className="text-xs text-[#848b9b]">
|
||||
Select an option, then pick a target node. This creates a loop-back or cross-reference.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
id="xref-option-select"
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border px-2 py-1.5 text-xs',
|
||||
'bg-card text-foreground'
|
||||
'flex-1 rounded-md border border-[#1e2130] px-2 py-1.5 text-xs',
|
||||
'bg-[#14161d] text-[#e2e5eb]'
|
||||
)}
|
||||
defaultValue=""
|
||||
>
|
||||
@@ -245,8 +245,8 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
<select
|
||||
id="xref-target-select"
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border px-2 py-1.5 text-xs',
|
||||
'bg-card text-foreground'
|
||||
'flex-1 rounded-md border border-[#1e2130] px-2 py-1.5 text-xs',
|
||||
'bg-[#14161d] text-[#e2e5eb]'
|
||||
)}
|
||||
defaultValue=""
|
||||
onChange={(e) => {
|
||||
@@ -280,7 +280,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
||||
|
||||
{/* Example hint for root node */}
|
||||
{isRootNode && (node.options?.length || 0) < 2 && (
|
||||
<div className="mt-3 rounded-md border border-dashed border-border bg-accent/50 p-3 text-xs text-muted-foreground">
|
||||
<div className="mt-3 rounded-md border border-dashed border-[#1e2130] bg-accent/50 p-3 text-xs text-[#848b9b]">
|
||||
<strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
|
||||
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
||||
<div className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Title <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -58,9 +58,9 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
||||
placeholder="e.g., VDA Successfully Registered"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
titleError ? 'border-red-400' : 'border-border'
|
||||
titleError ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
/>
|
||||
{titleError && (
|
||||
@@ -71,7 +71,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
||||
{/* Description */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
Description
|
||||
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
||||
</label>
|
||||
@@ -79,14 +79,14 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPreview(!showPreview)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
||||
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
{showPreview ? 'Edit' : 'Preview'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{showPreview && node.description ? (
|
||||
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
|
||||
<div className="mt-1 rounded-md border border-[#1e2130] bg-accent/50 p-3 text-sm">
|
||||
<MarkdownContent content={node.description} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -101,8 +101,8 @@ Document what was done and the outcome.
|
||||
**Close ticket as:** Resolved"
|
||||
rows={5}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
@@ -111,7 +111,7 @@ Document what was done and the outcome.
|
||||
|
||||
{/* Resolution Steps */}
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||
Resolution Steps
|
||||
<InfoTip text="Step-by-step instructions for resolving the issue" />
|
||||
</label>
|
||||
@@ -132,8 +132,8 @@ Document what was done and the outcome.
|
||||
onChange={(e) => handleUpdateStep(index, e.target.value)}
|
||||
placeholder={`Step ${index + 1}`}
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -99,7 +99,7 @@ function NodeListItem({
|
||||
decision: 'bg-blue-500/20 text-blue-400',
|
||||
action: 'bg-yellow-500/20 text-yellow-400',
|
||||
solution: 'bg-green-500/20 text-green-400',
|
||||
answer: 'bg-muted text-muted-foreground border border-dashed border-border'
|
||||
answer: 'bg-muted text-[#848b9b] border border-dashed border-[#1e2130]'
|
||||
}
|
||||
|
||||
const getNodeLabel = () => {
|
||||
@@ -132,7 +132,7 @@ function NodeListItem({
|
||||
<span
|
||||
key={i}
|
||||
className={cn(
|
||||
'inline-block w-5 text-center text-muted-foreground/50',
|
||||
'inline-block w-5 text-center text-[#848b9b]/50',
|
||||
showLine ? 'border-l border-muted-foreground/30' : ''
|
||||
)}
|
||||
>
|
||||
@@ -140,10 +140,10 @@ function NodeListItem({
|
||||
</span>
|
||||
))}
|
||||
{/* Render current level connector */}
|
||||
<span className="inline-block w-5 text-center text-muted-foreground/50 font-mono text-xs">
|
||||
<span className="inline-block w-5 text-center text-[#848b9b]/50 font-mono text-xs">
|
||||
{isLast ? '└' : '├'}
|
||||
</span>
|
||||
<span className="inline-block w-3 text-muted-foreground/50 font-mono text-xs">──</span>
|
||||
<span className="inline-block w-3 text-[#848b9b]/50 font-mono text-xs">──</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -187,7 +187,7 @@ function NodeListItem({
|
||||
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
|
||||
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
||||
: isSelected
|
||||
? 'bg-primary/10 ring-1 ring-primary'
|
||||
? 'bg-[rgba(34,211,238,0.10)] ring-1 ring-primary'
|
||||
: 'hover:bg-accent',
|
||||
hasError && 'ring-1 ring-destructive',
|
||||
hasWarning && !hasError && 'ring-1 ring-yellow-500'
|
||||
@@ -204,9 +204,9 @@ function NodeListItem({
|
||||
className="rounded p-0.5 hover:bg-muted"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronRight className="h-3.5 w-3.5 text-[#848b9b]" />
|
||||
) : (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronDown className="h-3.5 w-3.5 text-[#848b9b]" />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
@@ -216,7 +216,7 @@ function NodeListItem({
|
||||
{/* Drag handle */}
|
||||
{node.id !== 'root' && (
|
||||
<GripVertical
|
||||
className="h-4 w-4 cursor-grab text-muted-foreground opacity-0 group-hover:opacity-100"
|
||||
className="h-4 w-4 cursor-grab text-[#848b9b] opacity-0 group-hover:opacity-100"
|
||||
onMouseDown={() => { gripInitiated.current = true }}
|
||||
onMouseUp={() => { gripInitiated.current = false }}
|
||||
/>
|
||||
@@ -238,13 +238,13 @@ function NodeListItem({
|
||||
|
||||
{/* From option label */}
|
||||
{fromOption && (
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-[#848b9b]">
|
||||
{fromOption}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Node label */}
|
||||
<span className="flex-1 truncate text-foreground">
|
||||
<span className="flex-1 truncate text-[#e2e5eb]">
|
||||
{getNodeLabel()}
|
||||
</span>
|
||||
|
||||
@@ -272,7 +272,7 @@ function NodeListItem({
|
||||
|
||||
{/* Node ID */}
|
||||
<span
|
||||
className="hidden text-xs text-muted-foreground sm:inline cursor-help"
|
||||
className="hidden text-xs text-[#848b9b] sm:inline cursor-help"
|
||||
title={`Full ID: ${node.id}`}
|
||||
>
|
||||
{node.id === 'root' ? 'root' : node.id.slice(0, 8) + '...'}
|
||||
@@ -289,7 +289,7 @@ function NodeListItem({
|
||||
}}
|
||||
title="Add child node"
|
||||
aria-label="Add child node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -302,7 +302,7 @@ function NodeListItem({
|
||||
}}
|
||||
title="Edit node"
|
||||
aria-label="Edit node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -316,7 +316,7 @@ function NodeListItem({
|
||||
}}
|
||||
title="Duplicate node"
|
||||
aria-label="Duplicate node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -328,7 +328,7 @@ function NodeListItem({
|
||||
}}
|
||||
title="Delete node"
|
||||
aria-label="Delete node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-destructive/20 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -340,7 +340,7 @@ function NodeListItem({
|
||||
{/* Collapsed indicator */}
|
||||
{hasChildren && isCollapsed && (
|
||||
<div
|
||||
className="text-xs text-muted-foreground py-1"
|
||||
className="text-xs text-[#848b9b] py-1"
|
||||
style={{ marginLeft: `${(depth + 1) * 20 + 32}px` }}
|
||||
>
|
||||
<span className="rounded bg-muted px-2 py-0.5">
|
||||
@@ -536,22 +536,22 @@ export function NodeList() {
|
||||
|
||||
if (!treeStructure) {
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-card p-4 text-center text-sm text-muted-foreground">
|
||||
<div className="rounded-lg border border-[#1e2130] bg-[#14161d] p-4 text-center text-sm text-[#848b9b]">
|
||||
No tree structure. Add a root node to get started.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-card">
|
||||
<div className="flex items-center justify-between border-b border-border p-3">
|
||||
<div className="rounded-lg border border-[#1e2130] bg-[#14161d]">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] p-3">
|
||||
<h2 className="text-sm font-semibold text-card-foreground">Nodes</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAddingToParent(treeStructure.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium',
|
||||
'bg-primary text-primary-foreground hover:bg-primary/90'
|
||||
'bg-primary text-[#22d3ee]-foreground hover:bg-primary/90'
|
||||
)}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
@@ -581,8 +581,8 @@ export function NodeList() {
|
||||
|
||||
{/* Add Node Type Selector */}
|
||||
{addingToParent && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-xs">
|
||||
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#0c0d10]/80">
|
||||
<div className="w-full max-w-xs rounded-lg border border-[#1e2130] bg-[#14161d] p-4 shadow-lg">
|
||||
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
||||
<div className="space-y-2">
|
||||
<button
|
||||
@@ -596,7 +596,7 @@ export function NodeList() {
|
||||
<HelpCircle className="h-4 w-4 text-blue-500" />
|
||||
<div>
|
||||
<div className="font-medium">Decision</div>
|
||||
<div className="text-xs text-muted-foreground">Question with options</div>
|
||||
<div className="text-xs text-[#848b9b]">Question with options</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@@ -610,7 +610,7 @@ export function NodeList() {
|
||||
<Zap className="h-4 w-4 text-yellow-500" />
|
||||
<div>
|
||||
<div className="font-medium">Action</div>
|
||||
<div className="text-xs text-muted-foreground">Task to perform</div>
|
||||
<div className="text-xs text-[#848b9b]">Task to perform</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@@ -624,7 +624,7 @@ export function NodeList() {
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
<div>
|
||||
<div className="font-medium">Solution</div>
|
||||
<div className="text-xs text-muted-foreground">Resolution endpoint</div>
|
||||
<div className="text-xs text-[#848b9b]">Resolution endpoint</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -145,7 +145,7 @@ export function NodePicker({
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
@@ -153,8 +153,8 @@ export function NodePicker({
|
||||
{/* Inline node creation UI */}
|
||||
{creatingNodeType ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 rounded-md border border-border bg-accent/50 p-2">
|
||||
<span className="text-xs font-medium text-foreground">
|
||||
<div className="flex items-center gap-2 rounded-md border border-[#1e2130] bg-accent/50 p-2">
|
||||
<span className="text-xs font-medium text-[#e2e5eb]">
|
||||
New {NODE_TYPE_LABELS[creatingNodeType]}:
|
||||
</span>
|
||||
<input
|
||||
@@ -165,8 +165,8 @@ export function NodePicker({
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'}
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'flex-1 rounded-md border border-[#1e2130] px-2 py-1 text-sm',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -175,7 +175,7 @@ export function NodePicker({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancelCreate}
|
||||
className="flex-1 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex-1 rounded-md border border-[#1e2130] px-3 py-1.5 text-xs font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -185,7 +185,7 @@ export function NodePicker({
|
||||
disabled={!newNodeTitle.trim()}
|
||||
className={cn(
|
||||
'flex-1 rounded-md px-3 py-1.5 text-xs font-medium',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
@@ -200,9 +200,9 @@ export function NodePicker({
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'bg-[#14161d] text-[#e2e5eb]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
error ? 'border-red-400' : 'border-border'
|
||||
error ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
>
|
||||
<option value="">{placeholder}</option>
|
||||
@@ -250,7 +250,7 @@ export function NodePicker({
|
||||
|
||||
{/* Show what's selected */}
|
||||
{value && selectedNode && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
→ {selectedNode.label}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -64,14 +64,14 @@ interface AddNodePickerProps {
|
||||
|
||||
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-xs">
|
||||
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
|
||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-[#14161d] px-3 py-2 shadow-xs">
|
||||
<span className="text-xs text-[#848b9b] shrink-0">Add:</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelect('decision')}
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
||||
)}
|
||||
>
|
||||
@@ -83,7 +83,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||
type="button"
|
||||
onClick={() => onSelect('action')}
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
||||
)}
|
||||
>
|
||||
@@ -95,7 +95,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||
type="button"
|
||||
onClick={() => onSelect('solution')}
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
||||
)}
|
||||
>
|
||||
@@ -106,7 +106,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="ml-1 rounded p-0.5 text-muted-foreground hover:bg-accent"
|
||||
className="ml-1 rounded p-0.5 text-[#848b9b] hover:bg-accent"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -127,9 +127,9 @@ function AddNodeButton({ label = 'Add node', onClick }: AddNodeButtonProps) {
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-lg px-3 py-1.5 text-xs font-label',
|
||||
'border border-dashed border-border text-muted-foreground',
|
||||
'hover:border-primary/40 hover:text-foreground hover:bg-accent/50',
|
||||
'flex items-center gap-1 rounded-lg px-3 py-1.5 text-xs font-sans text-xs',
|
||||
'border border-dashed border-[#1e2130] text-[#848b9b]',
|
||||
'hover:border-primary/40 hover:text-[#e2e5eb] hover:bg-accent/50',
|
||||
'transition-all duration-150'
|
||||
)}
|
||||
>
|
||||
@@ -505,7 +505,7 @@ export function TreeCanvas() {
|
||||
|
||||
{/* Option label tag (above card, shown when this is a branch from a decision) */}
|
||||
{optionLabel && (
|
||||
<div className="mb-1 rounded bg-muted px-2 py-0.5 text-[10px] text-muted-foreground font-label">
|
||||
<div className="mb-1 rounded bg-muted px-2 py-0.5 text-[10px] text-[#848b9b] font-sans text-xs">
|
||||
{optionLabel}
|
||||
</div>
|
||||
)}
|
||||
@@ -547,7 +547,7 @@ export function TreeCanvas() {
|
||||
return (
|
||||
<div key={opt.id} className="flex flex-col items-center gap-1">
|
||||
<div className="h-4 w-px bg-border" />
|
||||
<span className="text-[10px] text-muted-foreground font-label">
|
||||
<span className="text-[10px] text-[#848b9b] font-sans text-xs">
|
||||
{opt.label || '(unlabeled option)'}
|
||||
</span>
|
||||
{pendingAddKey === key ? (
|
||||
@@ -594,7 +594,7 @@ export function TreeCanvas() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleToggleSubtreeCollapse(node.id)}
|
||||
className="rounded-full border border-dashed border-border bg-card px-3 py-1 text-[10px] text-muted-foreground font-label hover:border-primary/40 hover:text-foreground transition-colors"
|
||||
className="rounded-full border border-dashed border-[#1e2130] bg-[#14161d] px-3 py-1 text-[10px] text-[#848b9b] font-sans text-xs hover:border-primary/40 hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
{orderedChildren.length} node{orderedChildren.length !== 1 ? 's' : ''} hidden — click to expand
|
||||
</button>
|
||||
@@ -676,7 +676,7 @@ export function TreeCanvas() {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-muted-foreground text-sm">
|
||||
<div className="mb-2 text-[#848b9b] text-sm">
|
||||
No tree structure. Start by saving a tree name.
|
||||
</div>
|
||||
</div>
|
||||
@@ -698,7 +698,7 @@ export function TreeCanvas() {
|
||||
<div className="flex min-h-full min-w-full items-start justify-center p-8 pb-24">
|
||||
<div className="flex flex-col items-center">
|
||||
{/* START badge above root */}
|
||||
<div className="mb-2 rounded-full border border-border bg-card px-3 py-1 text-xs font-label text-muted-foreground">
|
||||
<div className="mb-2 rounded-full border border-[#1e2130] bg-[#14161d] px-3 py-1 text-xs font-sans text-xs text-[#848b9b]">
|
||||
START
|
||||
</div>
|
||||
<div className="mb-1 h-4 w-px bg-border" />
|
||||
|
||||
@@ -168,7 +168,7 @@ export function TreeCanvasNode({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative rounded-xl border border-border bg-card shadow-xs transition-all duration-150',
|
||||
'relative rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xs transition-all duration-150',
|
||||
config.borderClass,
|
||||
isExpanded && 'ring-1 ring-primary shadow-md',
|
||||
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
||||
@@ -186,7 +186,7 @@ export function TreeCanvasNode({
|
||||
'flex items-center gap-2 px-3 py-2.5',
|
||||
!isExpanded && 'cursor-pointer hover:bg-accent/50 rounded-t-xl',
|
||||
!isExpanded && 'rounded-xl',
|
||||
isExpanded && 'sticky top-0 z-10 bg-card rounded-t-xl'
|
||||
isExpanded && 'sticky top-0 z-10 bg-[#14161d] rounded-t-xl'
|
||||
)}
|
||||
onClick={!isExpanded ? handleCardClick : undefined}
|
||||
>
|
||||
@@ -200,20 +200,20 @@ export function TreeCanvasNode({
|
||||
onDragStart(e, node.id)
|
||||
}}
|
||||
>
|
||||
<GripVertical className="h-4 w-4 text-muted-foreground/50" />
|
||||
<GripVertical className="h-4 w-4 text-[#848b9b]/50" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Node type badge */}
|
||||
{isRoot ? (
|
||||
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold bg-blue-500/30 text-blue-400 font-label shrink-0">
|
||||
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold bg-blue-500/30 text-blue-400 font-sans text-xs shrink-0">
|
||||
<Play className="h-3 w-3" />
|
||||
START
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-label shrink-0',
|
||||
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-sans text-xs shrink-0',
|
||||
config.badgeClass
|
||||
)}
|
||||
>
|
||||
@@ -224,21 +224,21 @@ export function TreeCanvasNode({
|
||||
|
||||
{/* From-option label */}
|
||||
{fromOption && (
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground truncate max-w-[80px]">
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-[#848b9b] truncate max-w-[80px]">
|
||||
{fromOption}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Title text (compact mode) */}
|
||||
{!isExpanded && (
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">
|
||||
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">
|
||||
{getTitle()}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Options count badge */}
|
||||
{!isExpanded && getOptionsSummary() && (
|
||||
<span className="text-[10px] text-muted-foreground shrink-0 font-label">
|
||||
<span className="text-[10px] text-[#848b9b] shrink-0 font-sans text-xs">
|
||||
{getOptionsSummary()}
|
||||
</span>
|
||||
)}
|
||||
@@ -265,7 +265,7 @@ export function TreeCanvasNode({
|
||||
|
||||
{/* Unsaved badge */}
|
||||
{!isExpanded && isNew && (
|
||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-label shrink-0">
|
||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-sans text-xs shrink-0">
|
||||
Unsaved
|
||||
</span>
|
||||
)}
|
||||
@@ -276,7 +276,7 @@ export function TreeCanvasNode({
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); onToggleSubtreeCollapse() }}
|
||||
title={isSubtreeCollapsed ? 'Expand subtree' : 'Collapse subtree'}
|
||||
className="rounded p-0.5 text-muted-foreground/50 hover:bg-accent hover:text-foreground shrink-0"
|
||||
className="rounded p-0.5 text-[#848b9b]/50 hover:bg-accent hover:text-[#e2e5eb] shrink-0"
|
||||
>
|
||||
{isSubtreeCollapsed
|
||||
? <ChevronsUpDown className="h-3.5 w-3.5" />
|
||||
@@ -287,9 +287,9 @@ export function TreeCanvasNode({
|
||||
|
||||
{/* Expand/collapse chevron */}
|
||||
{!isExpanded ? (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
<ChevronRight className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||
) : (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
<ChevronDown className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||
)}
|
||||
|
||||
{/* Editing action buttons (expanded state) */}
|
||||
@@ -297,7 +297,7 @@ export function TreeCanvasNode({
|
||||
<div className="ml-auto flex items-center gap-1 shrink-0">
|
||||
{/* New badge */}
|
||||
{isNew && (
|
||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-label">
|
||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-sans text-xs">
|
||||
Unsaved
|
||||
</span>
|
||||
)}
|
||||
@@ -311,7 +311,7 @@ export function TreeCanvasNode({
|
||||
onDuplicate(node.id)
|
||||
}}
|
||||
title="Duplicate node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -326,7 +326,7 @@ export function TreeCanvasNode({
|
||||
onDelete(node.id)
|
||||
}}
|
||||
title="Delete node"
|
||||
className="rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-destructive/20 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -337,7 +337,7 @@ export function TreeCanvasNode({
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
title={isNew ? 'Cancel (deletes this node)' : 'Cancel changes'}
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -347,7 +347,7 @@ export function TreeCanvasNode({
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
title="Save changes"
|
||||
className="rounded p-1 bg-gradient-brand text-white hover:opacity-90"
|
||||
className="rounded p-1 bg-[#22d3ee] text-white hover:brightness-110"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -357,7 +357,7 @@ export function TreeCanvasNode({
|
||||
|
||||
{/* Expanded editing area */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border px-3 pb-3 pt-3 max-h-[70vh] overflow-y-auto">
|
||||
<div className="border-t border-[#1e2130] px-3 pb-3 pt-3 max-h-[70vh] overflow-y-auto">
|
||||
{/* Validation errors */}
|
||||
{(hasError || hasWarning) && (
|
||||
<div className="mb-3 space-y-1">
|
||||
|
||||
@@ -46,11 +46,11 @@ export function TreeEditorLayout({
|
||||
<>
|
||||
{/* Code Mode: Monaco editor (60%) + Preview (40%) — unchanged */}
|
||||
<div className={cn(
|
||||
'flex flex-col overflow-hidden border-border',
|
||||
'flex flex-col overflow-hidden border-[#1e2130]',
|
||||
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
||||
)}>
|
||||
<Suspense fallback={
|
||||
<div className="flex h-full items-center justify-center bg-card">
|
||||
<div className="flex h-full items-center justify-center bg-[#14161d]">
|
||||
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
||||
</div>
|
||||
}>
|
||||
|
||||
@@ -56,12 +56,12 @@ export function TreeMetadataForm() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4 bg-card border border-border rounded-2xl p-4">
|
||||
<h2 className="text-sm font-semibold text-foreground">Tree Details</h2>
|
||||
<div className="space-y-4 bg-[#14161d] border border-[#1e2130] rounded-2xl p-4">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb]">Tree Details</h2>
|
||||
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label htmlFor="tree-name" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="tree-name" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -72,9 +72,9 @@ export function TreeMetadataForm() {
|
||||
placeholder="e.g., VDA Registration Troubleshooting"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
nameError ? 'border-red-400' : 'border-border'
|
||||
nameError ? 'border-red-400' : 'border-[#1e2130]'
|
||||
)}
|
||||
/>
|
||||
{nameError && <p className="mt-1 text-xs text-red-400">{nameError.message}</p>}
|
||||
@@ -82,7 +82,7 @@ export function TreeMetadataForm() {
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label htmlFor="tree-description" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="tree-description" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
@@ -92,8 +92,8 @@ export function TreeMetadataForm() {
|
||||
placeholder="Brief description of what this tree troubleshoots..."
|
||||
rows={2}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -101,7 +101,7 @@ export function TreeMetadataForm() {
|
||||
|
||||
{/* Category */}
|
||||
<div>
|
||||
<label htmlFor="tree-category" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="tree-category" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Category
|
||||
</label>
|
||||
{!customCategory ? (
|
||||
@@ -110,8 +110,8 @@ export function TreeMetadataForm() {
|
||||
value={categoryId || ''}
|
||||
onChange={(e) => handleCategoryChange(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#14161d] text-[#e2e5eb]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -132,8 +132,8 @@ export function TreeMetadataForm() {
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
placeholder="Enter new category"
|
||||
className={cn(
|
||||
'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||
'block min-w-0 flex-1 rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
autoFocus
|
||||
@@ -145,7 +145,7 @@ export function TreeMetadataForm() {
|
||||
setCategory('')
|
||||
setCategoryId(null)
|
||||
}}
|
||||
className="shrink-0 rounded-md border border-border px-2.5 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="shrink-0 rounded-md border border-[#1e2130] px-2.5 py-2 text-xs text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -155,7 +155,7 @@ export function TreeMetadataForm() {
|
||||
|
||||
{/* Tags */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">Tags</label>
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">Tags</label>
|
||||
<div className="mt-1">
|
||||
<TagInput tags={tags} onChange={setTags} maxTags={10} placeholder="Add tags..." />
|
||||
</div>
|
||||
@@ -163,13 +163,13 @@ export function TreeMetadataForm() {
|
||||
|
||||
{/* Visibility */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">Visibility</label>
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">Visibility</label>
|
||||
<div className="mt-2 flex gap-4">
|
||||
<label
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
|
||||
'transition-colors',
|
||||
!isPublic ? 'border-primary/30 bg-accent text-foreground' : 'border-border text-muted-foreground hover:bg-accent/50'
|
||||
!isPublic ? 'border-primary/30 bg-accent text-[#e2e5eb]' : 'border-[#1e2130] text-[#848b9b] hover:bg-accent/50'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
@@ -186,7 +186,7 @@ export function TreeMetadataForm() {
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
|
||||
'transition-colors',
|
||||
isPublic ? 'border-primary/30 bg-accent text-foreground' : 'border-border text-muted-foreground hover:bg-accent/50'
|
||||
isPublic ? 'border-primary/30 bg-accent text-[#e2e5eb]' : 'border-[#1e2130] text-[#848b9b] hover:bg-accent/50'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
@@ -200,7 +200,7 @@ export function TreeMetadataForm() {
|
||||
<span className="text-sm">Public</span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
{isPublic
|
||||
? 'Anyone can view this tree'
|
||||
: 'Only you and your team can view this tree'}
|
||||
|
||||
@@ -76,8 +76,8 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing,
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
||||
isFixing
|
||||
? 'bg-primary/10 text-primary cursor-wait'
|
||||
: 'bg-gradient-brand text-white shadow-xs shadow-primary/20 hover:opacity-90'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] cursor-wait'
|
||||
: 'bg-[#22d3ee] text-white shadow-xs shadow-primary/20 hover:brightness-110'
|
||||
)}
|
||||
>
|
||||
{isFixing ? (
|
||||
@@ -114,7 +114,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing,
|
||||
<div className="flex-1">
|
||||
<p className="text-red-400">{error.message}</p>
|
||||
{error.nodeId && (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
<p className="mt-0.5 text-xs text-[#848b9b]">
|
||||
Click to select {itemLabel}: {error.nodeId}
|
||||
</p>
|
||||
)}
|
||||
@@ -138,7 +138,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing,
|
||||
<div className="flex-1">
|
||||
<p className="text-yellow-400">{warning.message}</p>
|
||||
{warning.nodeId && (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
<p className="mt-0.5 text-xs text-[#848b9b]">
|
||||
Click to select {itemLabel}: {warning.nodeId}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -167,7 +167,7 @@ export function CodeModeEditor() {
|
||||
beforeMount={handleEditorWillMount}
|
||||
onMount={handleEditorDidMount}
|
||||
loading={
|
||||
<div className="flex h-full items-center justify-center bg-card">
|
||||
<div className="flex h-full items-center justify-center bg-[#14161d]">
|
||||
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -91,15 +91,15 @@ export function CodeModeToolbar({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between border-b border-border bg-card px-3 py-1.5">
|
||||
<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-border px-2.5 py-1 text-xs font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'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" />
|
||||
@@ -107,24 +107,24 @@ export function CodeModeToolbar({
|
||||
<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-border bg-card py-1 shadow-xl">
|
||||
<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-muted-foreground hover:bg-accent"
|
||||
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-muted-foreground hover:bg-accent"
|
||||
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-muted-foreground hover:bg-accent"
|
||||
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
|
||||
@@ -139,8 +139,8 @@ export function CodeModeToolbar({
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
{isValidating ? (
|
||||
<>
|
||||
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
|
||||
<span className="text-muted-foreground">Validating...</span>
|
||||
<Loader2 className="h-3 w-3 animate-spin text-[#848b9b]" />
|
||||
<span className="text-[#848b9b]">Validating...</span>
|
||||
</>
|
||||
) : isValid ? (
|
||||
<>
|
||||
@@ -173,8 +173,8 @@ export function CodeModeToolbar({
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs',
|
||||
syntaxHelpOpen
|
||||
? 'bg-accent text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-muted-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:bg-accent hover:text-[#848b9b]'
|
||||
)}
|
||||
>
|
||||
<HelpCircle className="h-3 w-3" />
|
||||
|
||||
@@ -10,17 +10,17 @@ export function SyntaxHelpPanel({ open, onClose }: SyntaxHelpPanelProps) {
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col border-l border-border bg-card">
|
||||
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
||||
<span className="text-xs font-medium text-muted-foreground">Syntax Reference</span>
|
||||
<div className="flex h-full flex-col border-l border-[#1e2130] bg-[#14161d]">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-3 py-2">
|
||||
<span className="text-xs font-medium text-[#848b9b]">Syntax Reference</span>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-muted-foreground"
|
||||
className="rounded p-0.5 text-[#848b9b] hover:bg-accent hover:text-[#848b9b]"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-3 py-3 text-[11px] leading-relaxed text-muted-foreground">
|
||||
<div className="flex-1 overflow-y-auto px-3 py-3 text-[11px] leading-relaxed text-[#848b9b]">
|
||||
<Section title="Node Structure">
|
||||
<Code>{`---
|
||||
id: node_id
|
||||
@@ -82,7 +82,7 @@ Description paragraph.
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<h4 className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{title}</h4>
|
||||
<h4 className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-[#848b9b]">{title}</h4>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
@@ -91,8 +91,8 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
||||
function Code({ children }: { children: string }) {
|
||||
return (
|
||||
<pre className={cn(
|
||||
'mb-2 overflow-x-auto rounded-md border border-border bg-card px-2 py-1.5',
|
||||
'text-[10px] leading-relaxed text-muted-foreground whitespace-pre'
|
||||
'mb-2 overflow-x-auto rounded-md border border-[#1e2130] bg-[#14161d] px-2 py-1.5',
|
||||
'text-[10px] leading-relaxed text-[#848b9b] whitespace-pre'
|
||||
)}>
|
||||
{children}
|
||||
</pre>
|
||||
@@ -102,8 +102,8 @@ function Code({ children }: { children: string }) {
|
||||
function Row({ label, code }: { label: string; code: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-0.5">
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<code className="rounded bg-accent px-1 py-0.5 text-[10px] text-muted-foreground">{code}</code>
|
||||
<span className="text-[#848b9b]">{label}</span>
|
||||
<code className="rounded bg-accent px-1 py-0.5 text-[10px] text-[#848b9b]">{code}</code>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user