120 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
} |