Replace bare text "No X found" messages with the shared EmptyState component (icon + title + description + optional action). This gives empty moments a designed, intentional feel instead of a raw fallback. - EmptyState: use design system tokens (text-foreground, text-muted-foreground) - MarketplaceHome: EmptyState with ShoppingBag icon - MarketplaceViewGrid: EmptyState with clear filters action - ProfileViewTracksTab: EmptyState with Music icon - ProfileViewPlaylistsTab: EmptyState with ListMusic icon - PurchasesViewList: EmptyState with ShoppingCart icon - SessionManagement: centered empty text with padding Co-authored-by: Cursor <cursoragent@cursor.com>
174 lines
3.8 KiB
TypeScript
174 lines
3.8 KiB
TypeScript
import { ReactNode } from 'react';
|
|
import { Button } from './button';
|
|
import { Card, CardContent } from './card';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
// FE-COMP-003: Add empty states to all list views
|
|
|
|
/**
|
|
* EmptyStateProps - Propriétés du composant EmptyState
|
|
*
|
|
* @interface EmptyStateProps
|
|
*/
|
|
export interface EmptyStateProps {
|
|
/**
|
|
* Icône à afficher au-dessus du titre
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <EmptyState icon={<Inbox />} title="Aucun élément" />
|
|
* ```
|
|
*/
|
|
icon?: ReactNode;
|
|
|
|
/**
|
|
* Titre de l'état vide (requis)
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <EmptyState title="Aucun résultat trouvé" />
|
|
* ```
|
|
*/
|
|
title: string;
|
|
|
|
/**
|
|
* Description optionnelle sous le titre
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <EmptyState
|
|
* title="Aucun résultat"
|
|
* description="Essayez de modifier vos critères de recherche"
|
|
* />
|
|
* ```
|
|
*/
|
|
description?: string;
|
|
|
|
/**
|
|
* Action optionnelle (bouton) à afficher sous la description
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <EmptyState
|
|
* title="Aucun élément"
|
|
* action={{
|
|
* label: "Créer un élément",
|
|
* onClick: () => handleCreate(),
|
|
* variant: "primary"
|
|
* }}
|
|
* />
|
|
* ```
|
|
*/
|
|
action?: {
|
|
/** Texte du bouton */
|
|
label: string;
|
|
/** Fonction appelée lors du clic */
|
|
onClick: () => void;
|
|
/** Variant du bouton */
|
|
variant?: 'default' | 'outline' | 'ghost';
|
|
};
|
|
|
|
/**
|
|
* Classes CSS personnalisées
|
|
*/
|
|
className?: string;
|
|
|
|
/**
|
|
* Taille de l'état vide
|
|
*
|
|
* - `sm`: Petit (py-6, icône h-8 w-8)
|
|
* - `md`: Moyen (py-12, icône h-12 w-12) - par défaut
|
|
* - `lg`: Grand (py-16, icône h-16 w-16)
|
|
*
|
|
* @default 'md'
|
|
*/
|
|
size?: 'sm' | 'md' | 'lg';
|
|
}
|
|
|
|
/**
|
|
* EmptyState - Composant réutilisable pour afficher des états vides dans les listes
|
|
*
|
|
* Composant pour afficher un état vide lorsqu'une liste ou une section est vide.
|
|
* Design Kodo intégré avec support pour icône, titre, description et action.
|
|
*
|
|
* FE-COMP-003: Add empty states to all list views
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // État vide simple
|
|
* <EmptyState
|
|
* title="Aucun élément trouvé"
|
|
* description="Il n'y a pas encore d'éléments à afficher"
|
|
* />
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // État vide avec icône et action
|
|
* <EmptyState
|
|
* icon={<Inbox />}
|
|
* title="Aucun message"
|
|
* description="Vous n'avez pas encore de messages"
|
|
* action={{
|
|
* label: "Créer un message",
|
|
* onClick: () => handleCreate(),
|
|
* variant: "primary"
|
|
* }}
|
|
* />
|
|
* ```
|
|
*
|
|
* @component
|
|
* @param {EmptyStateProps} props - Propriétés du composant
|
|
* @returns {JSX.Element} Card contenant l'état vide stylisé
|
|
*/
|
|
export function EmptyState({
|
|
icon,
|
|
title,
|
|
description,
|
|
action,
|
|
className,
|
|
size = 'md',
|
|
}: EmptyStateProps) {
|
|
const sizeClasses = {
|
|
sm: 'py-6',
|
|
md: 'py-12',
|
|
lg: 'py-16',
|
|
};
|
|
|
|
const iconSizeClasses = {
|
|
sm: 'h-8 w-8',
|
|
md: 'h-12 w-12',
|
|
lg: 'h-16 w-16',
|
|
};
|
|
|
|
return (
|
|
<Card className={cn(className)}>
|
|
<CardContent className={cn('text-center', sizeClasses[size])}>
|
|
{icon && (
|
|
<div className="flex justify-center mb-4">
|
|
<div className={cn('text-muted-foreground', iconSizeClasses[size])}>
|
|
{icon}
|
|
</div>
|
|
</div>
|
|
)}
|
|
<h3 className="text-lg font-semibold mb-2 text-foreground font-display">
|
|
{title}
|
|
</h3>
|
|
{description && (
|
|
<p className="text-sm text-muted-foreground mb-4 max-w-md mx-auto">
|
|
{description}
|
|
</p>
|
|
)}
|
|
{action && (
|
|
<Button
|
|
onClick={action.onClick}
|
|
variant={action.variant || 'default'}
|
|
size={size === 'sm' ? 'sm' : 'default'}
|
|
>
|
|
{action.label}
|
|
</Button>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|