refactor(ui): migrar componente Badge a HeroUI
This commit is contained in:
1 parent
ac1249084a
commit
78f0f6d50a
1 file changed
+67
-68
+67
-68
@@ -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';
|
||||||
Reference in new issue
Block a user