feat: refactor workflow editor authoring surfaces
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { GitBranch, Plus, Trash2 } from 'lucide-react'
|
||||
|
||||
interface WorkflowListItem {
|
||||
id: string
|
||||
name: string
|
||||
isActive: boolean
|
||||
presetLabel: string
|
||||
presetClassName: string
|
||||
familyLabel: string
|
||||
familyClassName: string
|
||||
executionModeLabel: string
|
||||
executionModeClassName: string
|
||||
blueprintLabel?: string | null
|
||||
isReference?: boolean
|
||||
}
|
||||
|
||||
interface WorkflowListSection {
|
||||
key: string
|
||||
label: string
|
||||
className: string
|
||||
items: WorkflowListItem[]
|
||||
}
|
||||
|
||||
interface WorkflowListSidebarProps {
|
||||
isLoading: boolean
|
||||
sections: WorkflowListSection[]
|
||||
selectedId: string | null
|
||||
onSelectWorkflow: (workflowId: string) => void
|
||||
onCreateWorkflow: () => void
|
||||
onDeleteWorkflow: (workflowId: string, workflowName: string) => void
|
||||
}
|
||||
|
||||
export function WorkflowListSidebar({
|
||||
isLoading,
|
||||
sections,
|
||||
selectedId,
|
||||
onSelectWorkflow,
|
||||
onCreateWorkflow,
|
||||
onDeleteWorkflow,
|
||||
}: WorkflowListSidebarProps) {
|
||||
const workflowCount = sections.reduce((count, section) => count + section.items.length, 0)
|
||||
|
||||
return (
|
||||
<aside className="flex w-56 flex-shrink-0 flex-col border-r border-border-default bg-surface">
|
||||
<div className="flex items-center justify-between border-b border-border-default p-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-content-secondary">
|
||||
<GitBranch size={16} />
|
||||
Workflows
|
||||
</div>
|
||||
<button
|
||||
onClick={onCreateWorkflow}
|
||||
className="rounded p-1 text-content-muted hover:bg-surface-hover hover:text-content"
|
||||
title="New Workflow"
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-1 overflow-y-auto p-2">
|
||||
{isLoading && (
|
||||
<p className="px-2 py-4 text-center text-xs text-content-muted">Loading…</p>
|
||||
)}
|
||||
{!isLoading && workflowCount === 0 && (
|
||||
<div className="px-2 py-4 text-center">
|
||||
<p className="text-xs font-medium text-content-secondary">No workflows configured.</p>
|
||||
<p className="mt-1 text-xs text-content-muted">
|
||||
Workflows define the sequence of pipeline steps for rendering orders.
|
||||
</p>
|
||||
<button
|
||||
onClick={onCreateWorkflow}
|
||||
className="mt-2 text-xs text-accent hover:underline"
|
||||
>
|
||||
+ New Workflow
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{sections.map(section => (
|
||||
<div key={section.key} className="space-y-1 pb-2">
|
||||
<div className="flex items-center justify-between px-2 pt-1">
|
||||
<span className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${section.className}`}>
|
||||
{section.label}
|
||||
</span>
|
||||
<span className="text-xs text-content-muted">{section.items.length}</span>
|
||||
</div>
|
||||
{section.items.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelectWorkflow(item.id)}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
onSelectWorkflow(item.id)
|
||||
}
|
||||
}}
|
||||
className={`group w-full rounded-lg border px-3 py-2.5 text-left transition-colors focus:outline-none focus:ring-2 focus:ring-accent ${
|
||||
selectedId === item.id
|
||||
? 'border-accent/30 bg-accent-light'
|
||||
: 'border-transparent hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-1">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
{item.isActive && (
|
||||
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-green-500" title="Active" />
|
||||
)}
|
||||
<p className="truncate text-sm font-medium text-content">{item.name}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
onDeleteWorkflow(item.id, item.name)
|
||||
}}
|
||||
className="flex-shrink-0 rounded p-0.5 text-content-muted opacity-0 hover:bg-red-100 hover:text-red-600 group-hover:opacity-100"
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
</button>
|
||||
</div>
|
||||
<span className={`mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.presetClassName}`}>
|
||||
{item.presetLabel}
|
||||
</span>
|
||||
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.familyClassName}`}>
|
||||
{item.familyLabel}
|
||||
</span>
|
||||
{item.blueprintLabel && (
|
||||
<span className="ml-1 mt-1 inline-block rounded-full bg-slate-100 px-1.5 py-0.5 text-xs font-medium text-slate-700 dark:bg-slate-900/40 dark:text-slate-300">
|
||||
{item.blueprintLabel}
|
||||
</span>
|
||||
)}
|
||||
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.executionModeClassName}`}>
|
||||
{item.executionModeLabel}
|
||||
</span>
|
||||
{item.isReference && (
|
||||
<p className="mt-1 text-xs text-content-muted">
|
||||
Canonical reference workflow for parity work.
|
||||
</p>
|
||||
)}
|
||||
{!item.isActive && (
|
||||
<span className="ml-1 text-xs text-content-muted">(inactive)</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user