refactor(ui): migrar componente Badge a HeroUI

This commit is contained in:
Miguel Ortega committed 2026-09-11 20:24:42 -05:00
1 parent ac1249084a
commit 78f0f6d50a
1 file changed
+67 -68
+67 -68
View File
@@ -1,18 +1,49 @@
"use client"; "use client";
import React from 'react'; import React from 'react';
import { Chip as HeroUIChip, ChipProps as HeroUIChipProps } from '@heroui/react';
import { tv } from '@heroui/styles';
import { FiX } from 'react-icons/fi'; import { FiX } from 'react-icons/fi';
export interface BadgeProps { // 1. Organizamos los estilos con 'tv' usando variantes estándar
children: React.ReactNode; const badgeStyles = tv({
variant?: base: "inline-flex items-center select-none border transition-colors",
| 'default' variants: {
| 'primary' customVariant: {
| 'secondary' default: 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800/80 dark:text-slate-200 dark:border-slate-700',
| 'success' primary: 'bg-cyan-50 text-cyan-700 border-cyan-200 dark:bg-cyan-950/60 dark:text-cyan-300 dark:border-cyan-500/30',
| 'warning' secondary: 'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/60 dark:text-purple-300 dark:border-purple-500/30',
| 'danger' success: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border-emerald-500/30',
| 'info' warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/60 dark:text-amber-300 dark:border-amber-500/30',
| 'outline'; danger: 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/60 dark:text-rose-300 dark:border-rose-500/30',
info: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950/60 dark:text-sky-300 dark:border-sky-500/30',
outline: 'bg-transparent text-slate-700 border-slate-300 dark:text-slate-300 dark:border-slate-700',
},
customSize: {
sm: 'text-[10px] px-2 py-0.5 gap-1 rounded-md font-semibold h-auto min-w-min',
md: 'text-xs px-2.5 py-1 gap-1.5 rounded-lg font-medium h-auto min-w-min',
lg: 'text-sm px-3 py-1.5 gap-2 rounded-xl font-medium h-auto min-w-min',
}
},
defaultVariants: {
customVariant: "default",
customSize: "md",
}
});
// Mapeo auxiliar de colores para los dots según la variante
const dotColorMap: Record<NonNullable<BadgeProps['variant']>, string> = {
default: 'bg-slate-500',
primary: 'bg-cyan-500',
secondary: 'bg-purple-500',
success: 'bg-emerald-500',
warning: 'bg-amber-500',
danger: 'bg-rose-500',
info: 'bg-sky-500',
outline: 'bg-slate-400',
};
export interface BadgeProps extends Omit<HeroUIChipProps, 'variant' | 'size' | 'color' | 'className' | 'children'> {
variant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'outline';
size?: 'sm' | 'md' | 'lg'; size?: 'sm' | 'md' | 'lg';
dot?: boolean; dot?: boolean;
dotPulse?: boolean; dotPulse?: boolean;
@@ -20,50 +51,12 @@ export interface BadgeProps {
removable?: boolean; removable?: boolean;
onRemove?: () => void; onRemove?: () => void;
className?: string; className?: string;
children?: React.ReactNode;
} }
const variantStyles: Record<NonNullable<BadgeProps['variant']>, { badge: string; dot: string }> = { export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
default: { (
badge: 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800/80 dark:text-slate-200 dark:border-slate-700', {
dot: 'bg-slate-500',
},
primary: {
badge: 'bg-cyan-50 text-cyan-700 border-cyan-200 dark:bg-cyan-950/60 dark:text-cyan-300 dark:border-cyan-500/30',
dot: 'bg-cyan-500',
},
secondary: {
badge: 'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/60 dark:text-purple-300 dark:border-purple-500/30',
dot: 'bg-purple-500',
},
success: {
badge: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border-emerald-500/30',
dot: 'bg-emerald-500',
},
warning: {
badge: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/60 dark:text-amber-300 dark:border-amber-500/30',
dot: 'bg-amber-500',
},
danger: {
badge: 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/60 dark:text-rose-300 dark:border-rose-500/30',
dot: 'bg-rose-500',
},
info: {
badge: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950/60 dark:text-sky-300 dark:border-sky-500/30',
dot: 'bg-sky-500',
},
outline: {
badge: 'bg-transparent text-slate-700 border-slate-300 dark:text-slate-300 dark:border-slate-700',
dot: 'bg-slate-400',
},
};
const sizeStyles = {
sm: 'text-[10px] px-2 py-0.5 gap-1 rounded-md font-semibold',
md: 'text-xs px-2.5 py-1 gap-1.5 rounded-lg font-medium',
lg: 'text-sm px-3 py-1.5 gap-2 rounded-xl font-medium',
};
export const Badge: React.FC<BadgeProps> = ({
children, children,
variant = 'default', variant = 'default',
size = 'md', size = 'md',
@@ -73,27 +66,30 @@ export const Badge: React.FC<BadgeProps> = ({
removable = false, removable = false,
onRemove, onRemove,
className = '', className = '',
}) => { ...props
const currentVariant = variantStyles[variant]; },
ref
) => {
const dotColor = dotColorMap[variant];
return ( return (
<span <HeroUIChip
className={` ref={ref}
inline-flex items-center select-none border transition-colors // En HeroUI v3 se usa className en singular (soporta string directo desde tv)
${currentVariant.badge} className={badgeStyles({
${sizeStyles[size]} customVariant: variant,
${className} customSize: size,
`} className
})}
{...props}
> >
{/* Optional Dot indicator */} {/* Optional Dot indicator */}
{dot && ( {dot && (
<span className="relative flex h-1.5 w-1.5 shrink-0"> <span className="relative flex h-1.5 w-1.5 shrink-0">
{dotPulse && ( {dotPulse && (
<span <span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${dotColor}`} />
className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${currentVariant.dot}`}
/>
)} )}
<span className={`relative inline-flex rounded-full h-1.5 w-1.5 ${currentVariant.dot}`} /> <span className={`relative inline-flex rounded-full h-1.5 w-1.5 ${dotColor}`} />
</span> </span>
)} )}
@@ -117,6 +113,9 @@ export const Badge: React.FC<BadgeProps> = ({
<FiX className="text-xs" /> <FiX className="text-xs" />
</button> </button>
)} )}
</span> </HeroUIChip>
); );
}; }
);
Badge.displayName = 'Badge';