mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
refactor: update EmptyStateCard component for improved layout and functionality
- Removed unused props and adjusted styles for a more compact and centered design. - Enhanced the display of the icon, title, and description for better visibility. - Updated keyboard shortcut hint and AI suggestion action for improved user interaction. - Refined dismiss/minimize controls to appear on hover, enhancing the user experience.
This commit is contained in:
@@ -45,13 +45,10 @@ interface EmptyStateCardProps {
|
|||||||
|
|
||||||
export const EmptyStateCard = memo(function EmptyStateCard({
|
export const EmptyStateCard = memo(function EmptyStateCard({
|
||||||
columnId,
|
columnId,
|
||||||
columnTitle,
|
|
||||||
addFeatureShortcut,
|
addFeatureShortcut,
|
||||||
isFilteredEmpty = false,
|
isFilteredEmpty = false,
|
||||||
isReadOnly = false,
|
isReadOnly = false,
|
||||||
onAiSuggest,
|
onAiSuggest,
|
||||||
opacity = 100,
|
|
||||||
glassmorphism = true,
|
|
||||||
customConfig,
|
customConfig,
|
||||||
}: EmptyStateCardProps) {
|
}: EmptyStateCardProps) {
|
||||||
const [isDismissed, setIsDismissed] = useState(false);
|
const [isDismissed, setIsDismissed] = useState(false);
|
||||||
@@ -70,19 +67,17 @@ export const EmptyStateCard = memo(function EmptyStateCard({
|
|||||||
const showActions = !isReadOnly && !isFilteredEmpty;
|
const showActions = !isReadOnly && !isFilteredEmpty;
|
||||||
const showShortcut = columnId === 'backlog' && addFeatureShortcut && showActions;
|
const showShortcut = columnId === 'backlog' && addFeatureShortcut && showActions;
|
||||||
|
|
||||||
// Minimized state - compact indicator
|
// Minimized state - compact centered indicator
|
||||||
if (isMinimized) {
|
if (isMinimized) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMinimized(false)}
|
onClick={() => setIsMinimized(false)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full p-3 rounded-lg',
|
'w-full h-full min-h-[120px] flex-1',
|
||||||
'border-2 border-dashed border-border/40',
|
|
||||||
'bg-card/30 hover:bg-card/50',
|
|
||||||
'transition-all duration-200 ease-out',
|
|
||||||
'flex items-center justify-center gap-2',
|
'flex items-center justify-center gap-2',
|
||||||
'text-muted-foreground/60 hover:text-muted-foreground',
|
'text-muted-foreground/40 hover:text-muted-foreground/60',
|
||||||
'cursor-pointer group',
|
'cursor-pointer group',
|
||||||
|
'transition-colors duration-200',
|
||||||
'animate-in fade-in duration-300'
|
'animate-in fade-in duration-300'
|
||||||
)}
|
)}
|
||||||
data-testid={`empty-state-minimized-${columnId}`}
|
data-testid={`empty-state-minimized-${columnId}`}
|
||||||
@@ -104,114 +99,79 @@ export const EmptyStateCard = memo(function EmptyStateCard({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative rounded-xl overflow-hidden',
|
'w-full h-full min-h-[200px] flex-1',
|
||||||
'border-2 border-dashed border-border/50',
|
'flex flex-col items-center justify-center',
|
||||||
|
'text-center px-4',
|
||||||
'transition-all duration-300 ease-out',
|
'transition-all duration-300 ease-out',
|
||||||
'animate-in fade-in slide-in-from-top-2 duration-300',
|
'animate-in fade-in duration-300',
|
||||||
'hover:border-border/70'
|
'group'
|
||||||
)}
|
)}
|
||||||
data-testid={`empty-state-card-${columnId}`}
|
data-testid={`empty-state-card-${columnId}`}
|
||||||
>
|
>
|
||||||
{/* Background with opacity */}
|
{/* Dismiss/Minimize controls - appears on hover */}
|
||||||
<div
|
<div className="absolute top-2 right-2 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
className={cn('absolute inset-0 bg-card/50 -z-10', glassmorphism && 'backdrop-blur-sm')}
|
|
||||||
style={{ opacity: opacity / 100 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Dismiss/Minimize controls */}
|
|
||||||
<div className="absolute top-2 right-2 flex items-center gap-1 z-10">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMinimized(true)}
|
onClick={() => setIsMinimized(true)}
|
||||||
className="p-1 rounded-md text-muted-foreground/40 hover:text-muted-foreground hover:bg-muted/50 transition-colors"
|
className="p-1 rounded-md text-muted-foreground/30 hover:text-muted-foreground/60 transition-colors"
|
||||||
title="Minimize guidance"
|
title="Minimize guidance"
|
||||||
>
|
>
|
||||||
<EyeOff className="w-3.5 h-3.5" />
|
<EyeOff className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsDismissed(true)}
|
onClick={() => setIsDismissed(true)}
|
||||||
className="p-1 rounded-md text-muted-foreground/40 hover:text-muted-foreground hover:bg-muted/50 transition-colors"
|
className="p-1 rounded-md text-muted-foreground/30 hover:text-muted-foreground/60 transition-colors"
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 space-y-4">
|
{/* Icon */}
|
||||||
{/* Header with icon */}
|
<div className="mb-3 text-muted-foreground/30">
|
||||||
<div className="flex items-start gap-3">
|
<IconComponent className="w-8 h-8" />
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'w-10 h-10 rounded-lg flex items-center justify-center shrink-0',
|
|
||||||
'bg-primary/10 text-primary/70'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<IconComponent className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0 pt-0.5">
|
|
||||||
<h4 className="font-medium text-sm text-foreground/90 mb-1">
|
|
||||||
{isFilteredEmpty ? 'No Matching Items' : config.title}
|
|
||||||
</h4>
|
|
||||||
<p className="text-xs text-muted-foreground/70 leading-relaxed">
|
|
||||||
{isFilteredEmpty
|
|
||||||
? 'No features match your current filters. Try adjusting your filter criteria.'
|
|
||||||
: config.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Keyboard shortcut hint */}
|
|
||||||
{showShortcut && (
|
|
||||||
<div className="flex items-center gap-2 py-2 px-3 rounded-lg bg-muted/30 border border-border/30">
|
|
||||||
<span className="text-xs text-muted-foreground/70">
|
|
||||||
{config.shortcutHint || 'Press'}
|
|
||||||
</span>
|
|
||||||
<Kbd className="bg-background/80 border border-border/50 px-2 py-0.5 font-semibold">
|
|
||||||
{formatShortcut(addFeatureShortcut, true)}
|
|
||||||
</Kbd>
|
|
||||||
<span className="text-xs text-muted-foreground/70">to add a feature</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Example card preview */}
|
|
||||||
{config.exampleCard && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'p-3 rounded-lg',
|
|
||||||
'border border-dashed border-border/30',
|
|
||||||
'bg-muted/20',
|
|
||||||
'opacity-60'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="text-[10px] uppercase tracking-wider text-muted-foreground/50 mb-1">
|
|
||||||
{config.exampleCard.category}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm font-medium text-muted-foreground/60">
|
|
||||||
{config.exampleCard.title}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Action buttons */}
|
|
||||||
{showActions && config.primaryAction && config.primaryAction.actionType !== 'none' && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="w-full h-8 text-xs border-dashed"
|
|
||||||
onClick={handlePrimaryAction}
|
|
||||||
data-testid={`empty-state-primary-action-${columnId}`}
|
|
||||||
>
|
|
||||||
<Wand2 className="w-3.5 h-3.5 mr-1.5" />
|
|
||||||
{config.primaryAction.label}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Filtered empty state hint */}
|
|
||||||
{isFilteredEmpty && (
|
|
||||||
<p className="text-xs text-center text-muted-foreground/50 italic">
|
|
||||||
Clear filters to see all items
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h4 className="font-medium text-sm text-muted-foreground/50 mb-1">
|
||||||
|
{isFilteredEmpty ? 'No Matching Items' : config.title}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="text-xs text-muted-foreground/40 leading-relaxed max-w-[180px]">
|
||||||
|
{isFilteredEmpty ? 'No features match your current filters.' : config.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Keyboard shortcut hint for backlog */}
|
||||||
|
{showShortcut && (
|
||||||
|
<div className="flex items-center gap-1.5 mt-3 text-muted-foreground/40">
|
||||||
|
<span className="text-xs">Press</span>
|
||||||
|
<Kbd className="bg-muted/30 border-0 px-1.5 py-0.5 text-[10px] text-muted-foreground/50">
|
||||||
|
{formatShortcut(addFeatureShortcut, true)}
|
||||||
|
</Kbd>
|
||||||
|
<span className="text-xs">to add</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* AI Suggest action for backlog */}
|
||||||
|
{showActions && config.primaryAction && config.primaryAction.actionType === 'ai-suggest' && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="mt-4 h-7 text-xs text-muted-foreground/50 hover:text-muted-foreground/70"
|
||||||
|
onClick={handlePrimaryAction}
|
||||||
|
data-testid={`empty-state-primary-action-${columnId}`}
|
||||||
|
>
|
||||||
|
<Wand2 className="w-3 h-3 mr-1.5" />
|
||||||
|
{config.primaryAction.label}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Filtered empty state hint */}
|
||||||
|
{isFilteredEmpty && (
|
||||||
|
<p className="text-[10px] mt-2 text-muted-foreground/30 italic">
|
||||||
|
Clear filters to see all items
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user