- Replace all kodo-* color classes across ~100 TSX files: kodo-void → background, kodo-ink → card, kodo-graphite → muted, kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive, kodo-lime → success, kodo-red → destructive, kodo-gold → warning - Replace cyan-500, magenta-500, lime-500 default Tailwind colors with semantic equivalents (primary, destructive, success) - Fix WaveformVisualizer hardcoded hex colors to SUMI values - Delete global-effects.css (conflicting, redundant with index.css) Co-authored-by: Cursor <cursoragent@cursor.com>
224 lines
5.4 KiB
TypeScript
224 lines
5.4 KiB
TypeScript
import * as React from 'react';
|
|
import { X } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* BadgeProps - Propriétés du composant Badge
|
|
*
|
|
* @interface BadgeProps
|
|
* @extends React.HTMLAttributes<HTMLSpanElement>
|
|
*/
|
|
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
/**
|
|
* Texte du badge (alternative à children)
|
|
*/
|
|
label?: string;
|
|
|
|
/**
|
|
* Variant de couleur du badge
|
|
*
|
|
* - `cyan`: Cyan (par défaut)
|
|
* - `magenta`: Magenta
|
|
* - `lime`: Lime (vert)
|
|
* - `gold`: Or
|
|
* - `terminal`: Style terminal (monospace)
|
|
* - `default`, `primary`: Alias pour cyan
|
|
* - `success`: Alias pour lime
|
|
* - `warning`: Alias pour gold
|
|
* - `error`: Alias pour magenta
|
|
* - `secondary`: Alias pour magenta
|
|
*
|
|
* @default 'cyan'
|
|
*/
|
|
variant?:
|
|
| 'cyan'
|
|
| 'magenta'
|
|
| 'lime'
|
|
| 'gold'
|
|
| 'terminal'
|
|
| 'default'
|
|
| 'primary'
|
|
| 'success'
|
|
| 'warning'
|
|
| 'error'
|
|
| 'secondary';
|
|
|
|
/**
|
|
* Icône à afficher dans le badge
|
|
*/
|
|
icon?: React.ReactNode;
|
|
|
|
/**
|
|
* Taille du badge
|
|
*
|
|
* - `sm`: Petit (px-2 py-0.5 text-[10px])
|
|
* - `md`: Moyen (px-2.5 py-0.5 text-[10px]) - par défaut
|
|
* - `lg`: Grand (px-4 py-1 text-xs)
|
|
*
|
|
* @default 'md'
|
|
*/
|
|
size?: 'sm' | 'md' | 'lg';
|
|
|
|
/**
|
|
* Si `true`, affiche un point au lieu du texte
|
|
*
|
|
* @default false
|
|
*/
|
|
dot?: boolean;
|
|
|
|
/**
|
|
* Nombre à afficher dans le badge (pour notifications, etc.)
|
|
* Si fourni et > 0, affiche le nombre dans un badge secondaire
|
|
*/
|
|
count?: number;
|
|
|
|
/**
|
|
* Contenu du badge (alternative à label)
|
|
*/
|
|
children?: React.ReactNode;
|
|
|
|
/**
|
|
* Callback pour supprimer/fermer le badge.
|
|
* Quand défini, affiche un bouton de fermeture (X).
|
|
*/
|
|
onDismiss?: () => void;
|
|
|
|
/**
|
|
* Si `true`, ajoute une animation de pulsation au badge.
|
|
*
|
|
* @default false
|
|
*/
|
|
pulse?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Badge - Composant de badge avec design system Kodo
|
|
*
|
|
* Composant de badge pour afficher des labels, statuts ou compteurs.
|
|
* Supporte plusieurs variants de couleur, tailles et options d'affichage.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Badge simple
|
|
* <Badge label="Nouveau" />
|
|
*
|
|
* // Badge avec variant
|
|
* <Badge variant="success">Actif</Badge>
|
|
*
|
|
* // Badge avec icône
|
|
* <Badge icon={<Star />} label="Premium" />
|
|
*
|
|
* // Badge avec compteur
|
|
* <Badge count={5}>Notifications</Badge>
|
|
* ```
|
|
*
|
|
* @component
|
|
* @param {BadgeProps} props - Propriétés du composant
|
|
* @returns {JSX.Element} Élément span stylisé comme un badge
|
|
*/
|
|
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
(
|
|
{
|
|
label,
|
|
variant = 'cyan',
|
|
icon,
|
|
size = 'md',
|
|
dot,
|
|
count,
|
|
children,
|
|
className,
|
|
onDismiss,
|
|
pulse,
|
|
...props
|
|
},
|
|
ref,
|
|
) => {
|
|
// Map compatibility variants to Kodo variants
|
|
const variantMap: Record<string, string> = {
|
|
default: 'cyan',
|
|
primary: 'cyan',
|
|
success: 'lime',
|
|
warning: 'gold',
|
|
error: 'magenta',
|
|
secondary: 'magenta',
|
|
};
|
|
|
|
const actualVariant = variantMap[variant] || variant;
|
|
|
|
const styles: Record<string, string> = {
|
|
cyan: 'bg-muted/10 text-muted-foreground border-border/30',
|
|
magenta: 'bg-destructive/10 text-destructive border-destructive/30',
|
|
lime: 'bg-success/10 text-success border-success/30',
|
|
gold: 'bg-warning/10 text-warning border-warning/30',
|
|
terminal:
|
|
'bg-success/10 text-success border-success/30 font-mono',
|
|
};
|
|
|
|
const sizeStyles = {
|
|
sm: 'px-2 py-0.5 text-xs',
|
|
md: 'px-2.5 py-0.5 text-xs',
|
|
lg: 'px-4 py-1 text-xs',
|
|
};
|
|
|
|
const dotBgStyles: Record<string, string> = {
|
|
cyan: 'bg-muted-foreground',
|
|
magenta: 'bg-destructive',
|
|
lime: 'bg-success',
|
|
gold: 'bg-warning',
|
|
terminal: 'bg-success',
|
|
};
|
|
|
|
const displayText = label || children;
|
|
const badgeVariant = actualVariant as keyof typeof styles;
|
|
|
|
// Dot-only mode: colored circle without text
|
|
if (dot && !displayText && !icon && count === undefined) {
|
|
return (
|
|
<span
|
|
ref={ref}
|
|
className={cn(
|
|
'inline-block h-2 w-2 rounded-full',
|
|
dotBgStyles[badgeVariant] || dotBgStyles.cyan,
|
|
pulse && 'animate-pulse',
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span
|
|
ref={ref}
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 rounded-full font-bold uppercase tracking-widest border',
|
|
styles[badgeVariant] || styles.cyan,
|
|
sizeStyles[size],
|
|
pulse && 'animate-pulse',
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{dot && <span className="w-3 h-3 rounded-full bg-current" />}
|
|
{icon && <span className="w-3 h-3">{icon}</span>}
|
|
{displayText}
|
|
{count !== undefined && count > 0 && (
|
|
<span className="ml-1 px-1.5 py-0.5 rounded-full bg-current/20 text-xs">
|
|
{count}
|
|
</span>
|
|
)}
|
|
{onDismiss && (
|
|
<button
|
|
type="button"
|
|
onClick={(e) => { e.stopPropagation(); onDismiss(); }}
|
|
className="ml-1 -mr-0.5 inline-flex items-center justify-center rounded-full p-0.5 hover:bg-black/10 transition-colors"
|
|
aria-label="Remove"
|
|
>
|
|
<X className="h-3 w-3" />
|
|
</button>
|
|
)}
|
|
</span>
|
|
);
|
|
},
|
|
);
|
|
Badge.displayName = 'Badge';
|