Files

120 lines
3.4 KiB
TypeScript

/** Gemeinsame UI-Bausteine (Forms, Buttons, Badges) im einheitlichen Look. */
/**
* Einheitliches Badge fuer Status/Rollen-Labels.
* variant: neutral (Standard), success (aktiv), warning (Hinweis),
* danger (Fehler), accent (hervorgehoben, z. B. eigene Rolle).
*/
export function Badge({
children,
variant = 'neutral',
}: {
children: React.ReactNode;
variant?: 'neutral' | 'success' | 'warning' | 'danger' | 'accent';
}) {
const styles = {
neutral:
'bg-slate-200 text-slate-700 dark:bg-slate-700 dark:text-slate-200',
success:
'bg-slate-200 text-slate-800 ring-1 ring-slate-400 dark:bg-slate-600 dark:text-slate-100 dark:ring-slate-500',
warning:
'bg-slate-100 text-slate-700 ring-1 ring-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:ring-slate-600',
danger:
'bg-slate-800 text-slate-100 dark:bg-slate-100 dark:text-slate-900',
accent:
'bg-slate-700 text-slate-50 dark:bg-slate-300 dark:text-slate-900',
}[variant];
return (
<span
className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${styles}`}
>
{children}
</span>
);
}
export function Card({
children,
className = '',
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={`rounded-lg border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-700 dark:bg-slate-900 ${className}`}
>
{children}
</div>
);
}
export function Button({
children,
type = 'submit',
variant = 'primary',
disabled,
onClick,
...rest
}: {
children: React.ReactNode;
type?: 'submit' | 'button';
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
onClick?: () => void;
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'onClick' | 'disabled'>) {
const styles = {
primary: 'bg-slate-800 text-white hover:bg-slate-700 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200',
secondary:
'bg-slate-200 text-slate-900 hover:bg-slate-300 dark:bg-slate-700 dark:text-slate-100 dark:hover:bg-slate-600',
danger: 'bg-red-600 text-white hover:bg-red-700',
}[variant];
return (
<button
type={type}
disabled={disabled}
onClick={onClick}
{...rest}
className={`rounded-md px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${styles}`}
>
{children}
</button>
);
}
export function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
const { className = '', ...rest } = props;
return (
<input
{...rest}
className={`w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:border-slate-500 focus:outline-none focus:ring-1 focus:ring-slate-500 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100 dark:placeholder:text-slate-500 ${className}`}
/>
);
}
export function Label({ children, htmlFor }: { children: React.ReactNode; htmlFor?: string }) {
return (
<label
htmlFor={htmlFor}
className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
>
{children}
</label>
);
}
export function ErrorMessage({ message }: { message: string | null }) {
if (!message) {
return null;
}
return (
<p
role="alert"
className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-900/40 dark:text-red-300"
>
{message}
</p>
);
}