feat: enhance ideation dashboard and prompt components

- Added a helper function to map priority levels to badge variants in the IdeationDashboard.
- Improved UI elements in SuggestionCard for better spacing and visual hierarchy.
- Updated PromptCategoryGrid and PromptList components with enhanced hover effects and layout adjustments for a more responsive design.
- Refined button styles and interactions for better user experience across components.

These changes aim to improve the overall usability and aesthetics of the ideation view.
This commit is contained in:
Shirone
2026-01-15 23:01:12 +01:00
parent d9571bfb8d
commit 52cc82fb3f
3 changed files with 136 additions and 62 deletions

View File

@@ -19,6 +19,30 @@ import type { AnalysisSuggestion } from '@automaker/types';
// Helper for consistent pluralization of "idea/ideas"
const pluralizeIdea = (count: number) => `idea${count !== 1 ? 's' : ''}`;
// Helper to map priority to Badge variant
const getPriorityVariant = (
priority: string
):
| 'default'
| 'secondary'
| 'destructive'
| 'outline'
| 'success'
| 'warning'
| 'error'
| 'info' => {
switch (priority.toLowerCase()) {
case 'high':
return 'error';
case 'medium':
return 'warning';
case 'low':
return 'info';
default:
return 'secondary';
}
};
interface IdeationDashboardProps {
onGenerateIdeas: () => void;
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
@@ -39,37 +63,51 @@ function SuggestionCard({
isAdding: boolean;
}) {
return (
<Card className="transition-all hover:border-primary/50">
<CardContent className="p-4">
<Card className="group transition-all hover:border-primary/50 hover:shadow-sm">
<CardContent className="p-5">
<div className="flex items-start gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-start gap-2 mb-1">
<h4 className="font-medium shrink-0">{suggestion.title}</h4>
<div className="flex-1 min-w-0 space-y-3">
<div className="flex flex-col gap-2">
<div className="flex items-start justify-between gap-4">
<h4 className="font-semibold text-base leading-tight">{suggestion.title}</h4>
</div>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs whitespace-nowrap">
<Badge
variant={getPriorityVariant(suggestion.priority)}
className="text-xs font-medium capitalize"
>
{suggestion.priority}
</Badge>
<Badge variant="secondary" className="text-xs whitespace-nowrap">
<Badge
variant="secondary"
className="text-xs text-muted-foreground bg-secondary/40"
>
{job.prompt.title}
</Badge>
</div>
</div>
<p className="text-sm text-muted-foreground">{suggestion.description}</p>
<p className="text-sm text-muted-foreground leading-relaxed">
{suggestion.description}
</p>
{suggestion.rationale && (
<p className="text-xs text-muted-foreground mt-2 italic">{suggestion.rationale}</p>
<div className="relative pl-3 border-l-2 border-primary/20 mt-3 py-1">
<p className="text-xs text-muted-foreground/80 italic">{suggestion.rationale}</p>
</div>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
<div className="flex flex-col gap-2 shrink-0 pt-1">
<Button
size="sm"
variant="ghost"
onClick={onRemove}
onClick={onAccept}
disabled={isAdding}
className="text-muted-foreground hover:text-destructive"
className={cn(
'w-full gap-1.5 shadow-none transition-all',
isAdding ? 'opacity-80' : 'hover:ring-2 hover:ring-primary/20'
)}
>
<X className="w-4 h-4" />
</Button>
<Button size="sm" onClick={onAccept} disabled={isAdding} className="gap-1">
{isAdding ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
@@ -79,6 +117,15 @@ function SuggestionCard({
</>
)}
</Button>
<Button
size="sm"
variant="ghost"
onClick={onRemove}
disabled={isAdding}
className="w-full text-muted-foreground hover:text-destructive hover:bg-destructive/10 h-8"
>
Dismiss
</Button>
</div>
</div>
</CardContent>
@@ -91,19 +138,33 @@ function GeneratingCard({ job }: { job: GenerationJob }) {
const isError = job.status === 'error';
return (
<Card className={cn('transition-all', isError ? 'border-red-500/50' : 'border-blue-500/50')}>
<CardContent className="p-4">
<Card
className={cn(
'transition-all',
isError ? 'border-destructive/50' : 'border-blue-500/30 bg-blue-50/5 dark:bg-blue-900/5'
)}
>
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{isError ? (
<AlertCircle className="w-5 h-5 text-red-500" />
) : (
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
)}
<div className="flex items-center gap-4">
<div
className={cn(
'w-10 h-10 rounded-full flex items-center justify-center shrink-0',
isError ? 'bg-destructive/10 text-destructive' : 'bg-blue-500/10 text-blue-500'
)}
>
{isError ? (
<AlertCircle className="w-5 h-5" />
) : (
<Loader2 className="w-5 h-5 animate-spin" />
)}
</div>
<div>
<p className="font-medium">{job.prompt.title}</p>
<p className="text-sm text-muted-foreground">
{isError ? job.error || 'Failed to generate' : 'Generating ideas...'}
{isError
? job.error || 'Failed to generate'
: 'Analyzing codebase and generating ideas...'}
</p>
</div>
</div>
@@ -135,7 +196,7 @@ function TagFilter({
if (tags.length === 0) return null;
return (
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-2 py-2">
{tags.map((tag) => {
const isSelected = selectedTags.has(tag);
const count = tagCounts[tag] || 0;
@@ -144,28 +205,31 @@ function TagFilter({
key={tag}
onClick={() => onToggleTag(tag)}
className={cn(
'px-3 py-1.5 text-sm rounded-full border transition-all flex items-center gap-1.5',
'px-3.5 py-1.5 text-sm rounded-full border shadow-sm transition-all flex items-center gap-2',
isSelected
? 'bg-primary text-primary-foreground border-primary'
: 'bg-secondary/50 text-muted-foreground border-border hover:border-primary/50 hover:text-foreground'
? 'bg-primary text-primary-foreground border-primary ring-2 ring-primary/20'
: 'bg-card text-muted-foreground border-border hover:border-primary/50 hover:text-foreground hover:bg-accent/50'
)}
>
{tag}
<span className="font-medium">{tag}</span>
<span
className={cn(
'text-xs',
isSelected ? 'text-primary-foreground/70' : 'text-muted-foreground/70'
'text-xs py-0.5 px-1.5 rounded-full',
isSelected
? 'bg-primary-foreground/20 text-primary-foreground'
: 'bg-muted text-muted-foreground'
)}
>
({count})
{count}
</span>
</button>
);
})}
{selectedTags.size > 0 && <div className="h-8 w-px bg-border mx-1" />}
{selectedTags.size > 0 && (
<button
onClick={() => selectedTags.forEach((tag) => onToggleTag(tag))}
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors font-medium"
>
Clear filters
</button>

View File

@@ -69,17 +69,19 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
return (
<Card
key={category.id}
className="cursor-pointer transition-all hover:border-primary hover:shadow-md"
className="group cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-lg hover:-translate-y-1"
onClick={() => onSelect(category.id)}
>
<CardContent className="p-6">
<div className="flex flex-col items-center text-center gap-3">
<div className="p-4 rounded-full bg-primary/10">
<Icon className="w-8 h-8 text-primary" />
<div className="flex flex-col items-center text-center gap-4">
<div className="p-4 rounded-2xl bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110 transition-all duration-300">
<Icon className="w-8 h-8" />
</div>
<div>
<h3 className="font-semibold text-lg">{category.name}</h3>
<p className="text-muted-foreground text-sm mt-1">{category.description}</p>
<div className="space-y-2">
<h3 className="font-semibold text-lg leading-tight group-hover:text-primary transition-colors">
{category.name}
</h3>
<p className="text-muted-foreground text-sm">{category.description}</p>
</div>
</div>
</CardContent>

View File

@@ -141,43 +141,51 @@ export function PromptList({ category, onBack }: PromptListProps) {
return (
<Card
key={prompt.id}
className={`transition-all ${
className={`group transition-all duration-300 ${
isDisabled
? 'opacity-60 cursor-not-allowed'
: 'cursor-pointer hover:border-primary hover:shadow-md'
} ${isLoading || isGenerating ? 'border-blue-500 ring-1 ring-blue-500' : ''} ${
isStarted && !isGenerating ? 'border-green-500/50' : ''
? 'opacity-60 cursor-not-allowed bg-muted/50'
: 'cursor-pointer hover:border-primary hover:shadow-md hover:-translate-x-1'
} ${isLoading || isGenerating ? 'border-blue-500/50 ring-1 ring-blue-500/20 bg-blue-50/10' : ''} ${
isStarted && !isGenerating ? 'border-green-500/50 bg-green-50/10' : ''
}`}
onClick={() => !isDisabled && handleSelectPrompt(prompt)}
>
<CardContent className="p-5">
<div className="flex items-start gap-4">
<div className="flex items-start gap-5">
<div
className={`p-2 rounded-lg mt-0.5 ${
className={`p-3 rounded-xl shrink-0 transition-all duration-300 ${
isLoading || isGenerating
? 'bg-blue-500/10'
? 'bg-blue-500/10 text-blue-500'
: isStarted
? 'bg-green-500/10'
: 'bg-primary/10'
? 'bg-green-500/10 text-green-500'
: 'bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110'
}`}
>
{isLoading || isGenerating ? (
<Loader2 className="w-4 h-4 text-blue-500 animate-spin" />
<Loader2 className="w-5 h-5 animate-spin" />
) : isStarted ? (
<CheckCircle2 className="w-4 h-4 text-green-500" />
<CheckCircle2 className="w-5 h-5" />
) : (
<Lightbulb className="w-4 h-4 text-primary" />
<Lightbulb className="w-5 h-5" />
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold">{prompt.title}</h3>
<p className="text-muted-foreground text-sm mt-1">{prompt.description}</p>
<div className="flex-1 min-w-0 space-y-1">
<div className="flex items-center justify-between gap-2">
<h3 className="font-semibold text-lg group-hover:text-primary transition-colors">
{prompt.title}
</h3>
{isStarted && !isGenerating && (
<span className="text-xs font-medium text-green-600 bg-green-100 dark:bg-green-900/30 dark:text-green-400 px-2 py-0.5 rounded-full">
Generated
</span>
)}
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{prompt.description}
</p>
{(isLoading || isGenerating) && (
<p className="text-blue-500 text-sm mt-2">Generating in dashboard...</p>
)}
{isStarted && !isGenerating && (
<p className="text-green-500 text-sm mt-2">
Already generated - check dashboard
<p className="text-blue-500 text-sm font-medium animate-pulse pt-1">
Generating ideas...
</p>
)}
</div>