refactor(ui): migrate to shadcn/ui components and fix scroll issues

Migrate UI component library from custom implementations to shadcn/ui:
- Add shadcn/ui primitives (Button, Card, Dialog, Input, etc.)
- Replace custom styles with Tailwind CSS v4 theme configuration
- Remove custom-theme.css in favor of globals.css with @theme directive

Fix scroll overflow issues in multiple components:
- ProjectSelector: "New Project" button no longer overlays project list
- FolderBrowser: folder list now scrolls properly within modal
- AgentCard: log modal content stays within bounds
- ConversationHistory: conversation list scrolls correctly
- KanbanColumn: feature cards scroll within fixed height
- ScheduleModal: schedule form content scrolls properly

Key technical changes:
- Replace ScrollArea component with native overflow-y-auto divs
- Add min-h-0 to flex containers to allow proper shrinking
- Restructure dropdown layouts with flex-col for fixed footers

New files:
- ui/components.json (shadcn/ui configuration)
- ui/src/components/ui/* (20 UI primitive components)
- ui/src/lib/utils.ts (cn utility for class merging)
- ui/tsconfig.app.json (app-specific TypeScript config)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-26 18:25:55 +02:00
parent e45b5b064e
commit c917582a64
69 changed files with 4900 additions and 4287 deletions

View File

@@ -2,26 +2,27 @@ import { CheckCircle2, Circle, Loader2, MessageCircle } from 'lucide-react'
import type { Feature, ActiveAgent } from '../lib/types'
import { DependencyBadge } from './DependencyBadge'
import { AgentAvatar } from './AgentAvatar'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
interface FeatureCardProps {
feature: Feature
onClick: () => void
isInProgress?: boolean
allFeatures?: Feature[]
activeAgent?: ActiveAgent // Agent working on this feature
activeAgent?: ActiveAgent
}
// Generate consistent color for category using CSS variable references
// These map to the --color-neo-category-* variables defined in globals.css
// Generate consistent color for category
function getCategoryColor(category: string): string {
const colors = [
'var(--color-neo-category-pink)',
'var(--color-neo-category-cyan)',
'var(--color-neo-category-green)',
'var(--color-neo-category-yellow)',
'var(--color-neo-category-orange)',
'var(--color-neo-category-purple)',
'var(--color-neo-category-blue)',
'bg-pink-500',
'bg-cyan-500',
'bg-green-500',
'bg-yellow-500',
'bg-orange-500',
'bg-purple-500',
'bg-blue-500',
]
let hash = 0
@@ -38,86 +39,85 @@ export function FeatureCard({ feature, onClick, isInProgress, allFeatures = [],
const hasActiveAgent = !!activeAgent
return (
<button
<Card
onClick={onClick}
className={`
w-full text-left neo-card p-4 cursor-pointer relative
${isInProgress ? 'animate-pulse-neo' : ''}
${feature.passes ? 'border-neo-done' : ''}
${isBlocked && !feature.passes ? 'border-neo-danger opacity-80' : ''}
${hasActiveAgent ? 'ring-2 ring-neo-progress ring-offset-2' : ''}
cursor-pointer transition-all hover:border-primary py-3
${isInProgress ? 'animate-pulse' : ''}
${feature.passes ? 'border-primary/50' : ''}
${isBlocked && !feature.passes ? 'border-destructive/50 opacity-80' : ''}
${hasActiveAgent ? 'ring-2 ring-primary ring-offset-2' : ''}
`}
>
{/* Header */}
<div className="flex items-start justify-between gap-2 mb-2">
<div className="flex items-center gap-2">
<span
className="neo-badge"
style={{ backgroundColor: categoryColor, color: 'var(--color-neo-text-on-bright)' }}
>
{feature.category}
</span>
<DependencyBadge feature={feature} allFeatures={allFeatures} compact />
</div>
<span className="font-mono text-sm text-neo-text-secondary">
#{feature.priority}
</span>
</div>
{/* Name */}
<h3 className="font-display font-bold mb-1 line-clamp-2">
{feature.name}
</h3>
{/* Description */}
<p className="text-sm text-neo-text-secondary line-clamp-2 mb-3">
{feature.description}
</p>
{/* Agent working on this feature */}
{activeAgent && (
<div className="flex items-center gap-2 mb-3 py-2 px-2 rounded bg-[var(--color-neo-progress)]/10 border border-[var(--color-neo-progress)]/30">
<AgentAvatar name={activeAgent.agentName} state={activeAgent.state} size="sm" />
<div className="flex-1 min-w-0">
<div className="text-xs font-bold text-neo-progress">
{activeAgent.agentName} is working on this!
</div>
{activeAgent.thought && (
<div className="flex items-center gap-1 mt-0.5">
<MessageCircle size={10} className="text-neo-text-secondary shrink-0" />
<p className="text-[10px] text-neo-text-secondary truncate italic">
{activeAgent.thought}
</p>
</div>
)}
<CardContent className="p-4 space-y-3">
{/* Header */}
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-2">
<Badge className={`${categoryColor} text-white`}>
{feature.category}
</Badge>
<DependencyBadge feature={feature} allFeatures={allFeatures} compact />
</div>
<span className="font-mono text-sm text-muted-foreground">
#{feature.priority}
</span>
</div>
)}
{/* Status */}
<div className="flex items-center gap-2 text-sm">
{isInProgress ? (
<>
<Loader2 size={16} className="animate-spin text-neo-progress" />
<span className="text-neo-progress font-bold">Processing...</span>
</>
) : feature.passes ? (
<>
<CheckCircle2 size={16} className="text-neo-done" />
<span className="text-neo-done font-bold">Complete</span>
</>
) : isBlocked ? (
<>
<Circle size={16} className="text-neo-danger" />
<span className="text-neo-danger">Blocked</span>
</>
) : (
<>
<Circle size={16} className="text-neo-text-secondary" />
<span className="text-neo-text-secondary">Pending</span>
</>
{/* Name */}
<h3 className="font-semibold line-clamp-2">
{feature.name}
</h3>
{/* Description */}
<p className="text-sm text-muted-foreground line-clamp-2">
{feature.description}
</p>
{/* Agent working on this feature */}
{activeAgent && (
<div className="flex items-center gap-2 py-2 px-2 rounded-md bg-primary/10 border border-primary/30">
<AgentAvatar name={activeAgent.agentName} state={activeAgent.state} size="sm" />
<div className="flex-1 min-w-0">
<div className="text-xs font-semibold text-primary">
{activeAgent.agentName} is working on this!
</div>
{activeAgent.thought && (
<div className="flex items-center gap-1 mt-0.5">
<MessageCircle size={10} className="text-muted-foreground shrink-0" />
<p className="text-[10px] text-muted-foreground truncate italic">
{activeAgent.thought}
</p>
</div>
)}
</div>
</div>
)}
</div>
</button>
{/* Status */}
<div className="flex items-center gap-2 text-sm">
{isInProgress ? (
<>
<Loader2 size={16} className="animate-spin text-primary" />
<span className="text-primary font-medium">Processing...</span>
</>
) : feature.passes ? (
<>
<CheckCircle2 size={16} className="text-primary" />
<span className="text-primary font-medium">Complete</span>
</>
) : isBlocked ? (
<>
<Circle size={16} className="text-destructive" />
<span className="text-destructive">Blocked</span>
</>
) : (
<>
<Circle size={16} className="text-muted-foreground" />
<span className="text-muted-foreground">Pending</span>
</>
)}
</div>
</CardContent>
</Card>
)
}