Files
klz-cables.com/components/ui/Badge.tsx
2025-12-29 18:18:48 +01:00

162 lines
4.0 KiB
TypeScript

import React, { forwardRef, ReactNode, HTMLAttributes } from 'react';
import { cn } from '../../lib/utils';
// Badge variants
type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'neutral';
// Badge sizes
type BadgeSize = 'sm' | 'md' | 'lg';
// Badge props interface
interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
variant?: BadgeVariant;
size?: BadgeSize;
icon?: ReactNode;
iconPosition?: 'left' | 'right';
rounded?: boolean;
children?: ReactNode;
}
// Helper function to get variant styles
const getVariantStyles = (variant: BadgeVariant) => {
switch (variant) {
case 'primary':
return 'bg-primary text-white';
case 'secondary':
return 'bg-secondary text-white';
case 'success':
return 'bg-success text-white';
case 'warning':
return 'bg-warning text-gray-900';
case 'error':
return 'bg-danger text-white';
case 'info':
return 'bg-info text-white';
case 'neutral':
return 'bg-gray-200 text-gray-800';
default:
return 'bg-primary text-white';
}
};
// Helper function to get size styles
const getSizeStyles = (size: BadgeSize) => {
switch (size) {
case 'sm':
return 'text-xs px-2 py-0.5';
case 'md':
return 'text-sm px-3 py-1';
case 'lg':
return 'text-base px-4 py-1.5';
default:
return 'text-sm px-3 py-1';
}
};
// Helper function to get icon spacing
const getIconSpacing = (size: BadgeSize, iconPosition: 'left' | 'right') => {
const spacing = {
sm: iconPosition === 'left' ? 'mr-1' : 'ml-1',
md: iconPosition === 'left' ? 'mr-1.5' : 'ml-1.5',
lg: iconPosition === 'left' ? 'mr-2' : 'ml-2',
};
return spacing[size];
};
// Helper function to get icon size
const getIconSize = (size: BadgeSize) => {
const sizeClasses = {
sm: 'w-3 h-3',
md: 'w-4 h-4',
lg: 'w-5 h-5',
};
return sizeClasses[size];
};
// Main Badge Component
export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
(
{
variant = 'primary',
size = 'md',
icon,
iconPosition = 'left',
rounded = true,
className = '',
children,
...props
},
ref
) => {
return (
<div
ref={ref}
className={cn(
// Base styles
'inline-flex items-center justify-center font-medium',
'transition-all duration-200 ease-in-out',
// Variant styles
getVariantStyles(variant),
// Size styles
getSizeStyles(size),
// Border radius
rounded ? 'rounded-full' : 'rounded-md',
// Custom classes
className
)}
{...props}
>
{/* Icon - Left position */}
{icon && iconPosition === 'left' && (
<span className={cn('flex items-center justify-center', getIconSpacing(size, 'left'), getIconSize(size))}>
{icon}
</span>
)}
{/* Badge content */}
{children && <span>{children}</span>}
{/* Icon - Right position */}
{icon && iconPosition === 'right' && (
<span className={cn('flex items-center justify-center', getIconSpacing(size, 'right'), getIconSize(size))}>
{icon}
</span>
)}
</div>
);
}
);
Badge.displayName = 'Badge';
// Badge Group Component for multiple badges
interface BadgeGroupProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
gap?: 'xs' | 'sm' | 'md' | 'lg';
}
export const BadgeGroup = forwardRef<HTMLDivElement, BadgeGroupProps>(
({ gap = 'sm', className = '', children, ...props }, ref) => {
const gapClasses = {
xs: 'gap-1',
sm: 'gap-2',
md: 'gap-3',
lg: 'gap-4',
};
return (
<div
ref={ref}
className={cn('flex flex-wrap items-center', gapClasses[gap], className)}
{...props}
>
{children}
</div>
);
}
);
BadgeGroup.displayName = 'BadgeGroup';
// Export types for external use
export type { BadgeProps, BadgeVariant, BadgeSize, BadgeGroupProps };