geraete/components/ui/Badge.tsx
2025-11-17 15:26:43 +01:00

162 lines
4.8 KiB
TypeScript

// components/ui/Badge.tsx
'use client';
import clsx from 'clsx';
import * as React from 'react';
type BadgeTone =
| 'gray'
| 'red'
| 'yellow'
| 'green'
| 'blue'
| 'indigo'
| 'purple'
| 'pink';
type BadgeVariant = 'border' | 'flat';
type BadgeShape = 'rounded' | 'pill';
type BadgeSize = 'sm' | 'md';
export type BadgeProps = {
children: React.ReactNode;
tone?: BadgeTone;
variant?: BadgeVariant;
shape?: BadgeShape;
size?: BadgeSize;
/** Zeigt vorne einen farbigen Punkt an */
dot?: boolean;
/** Wenn gesetzt, wird rechts ein Remove-Button angezeigt */
onRemove?: () => void;
className?: string;
};
/* ───────── Style-Mappings ───────── */
const baseClasses =
'inline-flex items-center text-xs font-medium';
const sizeClasses: Record<BadgeSize, string> = {
md: 'px-2 py-1',
sm: 'px-1.5 py-0.5',
};
const shapeClasses: Record<BadgeShape, string> = {
rounded: 'rounded-md',
pill: 'rounded-full',
};
const borderToneClasses: Record<BadgeTone, string> = {
gray:
'bg-gray-50 text-gray-600 inset-ring inset-ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:inset-ring-gray-400/20',
red:
'bg-red-50 text-red-700 inset-ring inset-ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:inset-ring-red-400/20',
yellow:
'bg-yellow-50 text-yellow-800 inset-ring inset-ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:inset-ring-yellow-400/20',
green:
'bg-green-50 text-green-700 inset-ring inset-ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:inset-ring-green-500/20',
blue:
'bg-blue-50 text-blue-700 inset-ring inset-ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:inset-ring-blue-400/30',
indigo:
'bg-indigo-50 text-indigo-700 inset-ring inset-ring-indigo-700/10 dark:bg-indigo-400/10 dark:text-indigo-400 dark:inset-ring-indigo-400/30',
purple:
'bg-purple-50 text-purple-700 inset-ring inset-ring-purple-700/10 dark:bg-purple-400/10 dark:text-purple-400 dark:inset-ring-purple-400/30',
pink:
'bg-pink-50 text-pink-700 inset-ring inset-ring-pink-700/10 dark:bg-pink-400/10 dark:text-pink-400 dark:inset-ring-pink-400/20',
};
const flatToneClasses: Record<BadgeTone, string> = {
gray:
'bg-gray-100 text-gray-600 dark:bg-gray-400/10 dark:text-gray-400',
red:
'bg-red-100 text-red-700 dark:bg-red-400/10 dark:text-red-400',
yellow:
'bg-yellow-100 text-yellow-800 dark:bg-yellow-400/10 dark:text-yellow-500',
green:
'bg-green-100 text-green-700 dark:bg-green-400/10 dark:text-green-400',
blue:
'bg-blue-100 text-blue-700 dark:bg-blue-400/10 dark:text-blue-400',
indigo:
'bg-indigo-100 text-indigo-700 dark:bg-indigo-400/10 dark:text-indigo-400',
purple:
'bg-purple-100 text-purple-700 dark:bg-purple-400/10 dark:text-purple-400',
pink:
'bg-pink-100 text-pink-700 dark:bg-pink-400/10 dark:text-pink-400',
};
const dotToneClasses: Record<BadgeTone, string> = {
gray: 'fill-gray-400',
red: 'fill-red-500 dark:fill-red-400',
yellow: 'fill-yellow-500 dark:fill-yellow-400',
green: 'fill-green-500 dark:fill-green-400',
blue: 'fill-blue-500 dark:fill-blue-400',
indigo: 'fill-indigo-500 dark:fill-indigo-400',
purple: 'fill-purple-500 dark:fill-purple-400',
pink: 'fill-pink-500 dark:fill-pink-400',
};
/* ───────── Component ───────── */
export default function Badge({
children,
tone = 'gray',
variant = 'border',
shape = 'rounded',
size = 'md',
dot = false,
onRemove,
className,
}: BadgeProps) {
const toneClasses =
variant === 'border'
? borderToneClasses[tone]
: flatToneClasses[tone];
const hasRemove = typeof onRemove === 'function';
return (
<span
className={clsx(
baseClasses,
sizeClasses[size],
shapeClasses[shape],
toneClasses,
dot && 'gap-x-1.5',
hasRemove && 'gap-x-0.5',
className,
)}
>
{/* Dot (optional) */}
{dot && (
<svg
viewBox="0 0 6 6"
aria-hidden="true"
className={clsx('size-1.5', dotToneClasses[tone])}
>
<circle r={3} cx={3} cy={3} />
</svg>
)}
<span>{children}</span>
{/* Remove-Button (optional) */}
{hasRemove && (
<button
type="button"
onClick={onRemove}
className="group relative -mr-1 size-3.5 rounded-xs hover:bg-black/5 dark:hover:bg-white/10"
>
<span className="sr-only">Entfernen</span>
<svg
viewBox="0 0 14 14"
className="size-3.5 stroke-current/60 group-hover:stroke-current/90"
>
<path d="M4 4l6 6m0-6l-6 6" />
</svg>
<span className="absolute -inset-1" />
</button>
)}
</span>
);
}