turash/bugulma/frontend/components/ui/Badge.tsx
2025-12-15 10:06:41 +01:00

202 lines
6.5 KiB
TypeScript

import { motion } from 'framer-motion';
import React from 'react';
type BadgeVariant =
| 'default'
| 'material'
| 'outline'
| 'secondary'
| 'destructive'
| 'success'
| 'construction'
| 'production'
| 'recreation'
| 'logistics'
| 'protected'
| 'cultural-heritage'
| 'historical';
interface BadgeProps {
children: React.ReactNode;
variant?: BadgeVariant;
showDot?: boolean;
dotColor?: 'primary' | 'accent';
textColor?: 'primary' | 'accent' | 'foreground';
size?: 'sm' | 'md' | 'lg';
className?: string;
animate?: boolean;
}
const Badge: React.FC<BadgeProps> = ({
children,
variant = 'default',
showDot = false,
dotColor = 'primary',
textColor,
size = 'md',
className = '',
animate = false,
}) => {
const sizeClasses = {
sm: 'px-3 py-1 text-xs',
md: 'px-4 py-1.5 lg:px-6 lg:py-2.5 text-sm lg:text-base',
lg: 'px-5 py-2 lg:px-7 lg:py-3 text-base lg:text-lg',
};
const textColorClasses = {
primary: 'text-primary',
accent: 'text-accent',
foreground: 'text-foreground',
};
const dotColorClasses = {
primary: 'bg-primary',
accent: 'bg-accent',
};
const isHeritageVariant =
variant === 'protected' || variant === 'cultural-heritage' || variant === 'historical';
const baseClasses =
variant === 'material'
? `relative inline-flex items-center gap-2.5 rounded-full font-medium overflow-hidden border-2 ${sizeClasses[size]} ${className}`
: `relative inline-flex items-center gap-2 rounded-full font-medium ${isHeritageVariant ? '' : 'border'} ${sizeClasses[size]} ${className}`;
if (variant === 'material') {
const finalTextColor = textColor || 'accent';
const finalDotColor = dotColor;
const BadgeContent = (
<>
{/* Subtle texture overlay */}
<div
className="absolute inset-0 opacity-[0.04] pointer-events-none rounded-full"
style={{
backgroundImage: `radial-gradient(circle at 1px 1px, var(--foreground) 1px, transparent 0)`,
backgroundSize: '8px 8px',
}}
/>
{showDot && (
<motion.span
className={`relative z-10 h-2.5 w-2.5 rounded-full shrink-0 ${dotColorClasses[finalDotColor]}`}
style={{
background: `radial-gradient(circle at 30% 30%, var(--${finalDotColor}), var(--${finalDotColor}))`,
boxShadow: `
0 2px 4px rgba(0, 0, 0, 0.2),
inset 0 1px 2px rgba(255, 255, 255, 0.4),
inset 0 -1px 1px rgba(0, 0, 0, 0.2)
`,
}}
animate={animate ? { scale: [1, 1.2, 1], opacity: [1, 0.9, 1] } : {}}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
/>
)}
<span
className={`relative z-10 ${textColorClasses[finalTextColor]} font-semibold drop-shadow-sm`}
>
{children}
</span>
</>
);
if (animate) {
return (
<motion.div
className={`${baseClasses} bg-muted`}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
whileHover={{ scale: 1.03, y: -2 }}
style={{
background:
'linear-gradient(135deg, var(--muted) 0%, var(--card) 50%, var(--muted) 100%)',
borderColor: 'var(--border)',
boxShadow: `
0 2px 4px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(0, 0, 0, 0.08),
0 8px 16px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(0, 0, 0, 0.05),
inset 0 -2px 4px rgba(0, 0, 0, 0.1)
`,
}}
>
{BadgeContent}
</motion.div>
);
}
return (
<div
className={`${baseClasses} bg-muted`}
style={{
background:
'linear-gradient(135deg, var(--muted) 0%, var(--card) 50%, var(--muted) 100%)',
borderColor: 'var(--border)',
boxShadow: `
0 2px 4px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(0, 0, 0, 0.08),
0 8px 16px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(0, 0, 0, 0.05),
inset 0 -2px 4px rgba(0, 0, 0, 0.1)
`,
}}
>
{BadgeContent}
</div>
);
}
// Variant classes for simple badges
const variantClasses: Record<string, string> = {
default: 'bg-primary border-transparent text-primary-foreground',
outline: 'bg-transparent border-border text-foreground',
secondary: 'bg-secondary border-transparent text-secondary-foreground',
destructive: 'bg-destructive border-transparent text-destructive-foreground',
success: 'bg-success border-success/30 text-success-foreground',
// Sector variants
construction:
'bg-sector-construction/10 border-sector-construction/20 text-sector-construction',
production: 'bg-sector-production/10 border-sector-production/20 text-sector-production',
recreation: 'bg-sector-recreation/10 border-sector-recreation/20 text-sector-recreation',
logistics: 'bg-sector-logistics/10 border-sector-logistics/20 text-sector-logistics',
};
// Heritage status variants need inline styles for proper CSS variable usage
// Using CSS color-mix for proper theme-aware colors with opacity
const heritageStyles: Record<string, React.CSSProperties> = {
protected: {
backgroundColor: 'color-mix(in srgb, var(--primary) 10%, transparent)',
borderColor: 'color-mix(in srgb, var(--primary) 40%, transparent)',
color: 'var(--primary)',
},
'cultural-heritage': {
backgroundColor: 'color-mix(in srgb, var(--accent) 10%, transparent)',
borderColor: 'color-mix(in srgb, var(--accent) 40%, transparent)',
color: 'var(--accent)',
},
historical: {
backgroundColor: 'color-mix(in srgb, var(--warning) 10%, transparent)',
borderColor: 'color-mix(in srgb, var(--warning) 40%, transparent)',
color: 'var(--warning)',
},
};
// For heritage variants, apply inline styles directly
const heritageStyle = isHeritageVariant ? heritageStyles[variant] : undefined;
return (
<span
className={`${baseClasses} ${!isHeritageVariant ? variantClasses[variant] || variantClasses.default : ''} ${className}`}
style={
heritageStyle ? { ...heritageStyle, borderWidth: '1px', borderStyle: 'solid' } : undefined
}
>
{showDot && <span className={`h-2 w-2 rounded-full shrink-0 ${dotColorClasses[dotColor]}`} />}
{children}
</span>
);
};
export default Badge;