Mobile PWA port of the desktop console: bottom-sheet modals, card-list record views, bottom tab nav, vite-plugin-pwa (manifest, service worker, icons). API layer, workflow UIDs, and design tokens reused verbatim.
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { cn } from '../../lib/cn';
|
|
|
|
export type BadgeTone =
|
|
| 'neutral'
|
|
| 'navy'
|
|
| 'success'
|
|
| 'warning'
|
|
| 'danger'
|
|
| 'info'
|
|
| 'accent';
|
|
|
|
export interface BadgeProps {
|
|
children?: ReactNode;
|
|
/** @default "neutral" */
|
|
tone?: BadgeTone;
|
|
/** Show a leading status dot. @default false */
|
|
dot?: boolean;
|
|
/** @default "md" */
|
|
size?: 'sm' | 'md';
|
|
className?: string;
|
|
}
|
|
|
|
const TONES: Record<BadgeTone, { wrap: string; dot: string }> = {
|
|
neutral: { wrap: 'bg-slate-100 text-slate-700', dot: 'bg-slate-500' },
|
|
navy: { wrap: 'bg-navy-900 text-white', dot: 'bg-sunrise-400' },
|
|
success: { wrap: 'bg-emerald-100 text-emerald-700', dot: 'bg-emerald-600' },
|
|
warning: { wrap: 'bg-amber-100 text-amber-700', dot: 'bg-amber-600' },
|
|
danger: { wrap: 'bg-ruby-100 text-ruby-700', dot: 'bg-ruby-600' },
|
|
info: { wrap: 'bg-sky-100 text-sky-700', dot: 'bg-sky-600' },
|
|
accent: { wrap: 'bg-sunrise-100 text-sunrise-600', dot: 'bg-sunrise-500' },
|
|
};
|
|
|
|
/** Compact status pill with optional leading dot. */
|
|
export function Badge({ children, tone = 'neutral', dot = false, size = 'md', className }: BadgeProps) {
|
|
const t = TONES[tone];
|
|
const sm = size === 'sm';
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 rounded-pill font-sans font-semibold leading-none whitespace-nowrap',
|
|
sm ? 'text-2xs px-2 py-1' : 'text-xs px-2.5 py-[5px]',
|
|
t.wrap,
|
|
className,
|
|
)}
|
|
>
|
|
{dot && <span className={cn('w-1.5 h-1.5 rounded-full', t.dot)} />}
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|