import type { AriaAttributes, ReactNode } from "react";
import { cn } from "./cn";
import { Icon, type IconName } from "./Icon";
export type Tone = "red" | "amber" | "green" | "blue" | "indigo" | "purple" | "pink" | "grey";
// Literal strings so Tailwind finds every class.
const tint: Record<Tone, { fill: string; icon: string; dot: string }> = {
red: { fill: "bg-red-fill text-red-text", icon: "text-red-icon", dot: "bg-red-icon" },
amber: { fill: "bg-amber-fill text-amber-text", icon: "text-amber-icon", dot: "bg-amber-icon" },
green: { fill: "bg-green-fill text-green-text", icon: "text-green-icon", dot: "bg-green-icon" },
blue: { fill: "bg-blue-fill text-blue-text", icon: "text-blue-icon", dot: "bg-blue-icon" },
indigo: { fill: "bg-indigo-fill text-indigo-text", icon: "text-indigo-icon", dot: "bg-indigo-icon" },
purple: { fill: "bg-purple-fill text-purple-text", icon: "text-purple-icon", dot: "bg-purple-icon" },
pink: { fill: "bg-pink-fill text-pink-text", icon: "text-pink-icon", dot: "bg-pink-icon" },
grey: { fill: "bg-grey-fill text-grey-text", icon: "text-grey-icon", dot: "bg-grey-icon" },
};
export const toneText = (t: Tone) => tint[t].icon;
export const toneDot = (t: Tone) => tint[t].dot;
/** A state: tinted, a filled icon and always its words, so colour is never the only signal. */
export function StatusPill({
tone,
icon,
children,
outline = false,
size = "md",
title,
className,
}: {
tone: Tone;
icon?: IconName;
children: ReactNode;
outline?: boolean;
size?: "sm" | "md";
title?: string;
className?: string;
}) {
return (
<span
title={title}
className={cn(
"inline-flex items-center gap-[5px] rounded-full font-medium whitespace-nowrap",
size === "md" ? "h-[26px] pr-2.5 pl-1.5 text-[13px]" : "h-[22px] pr-2 pl-1 text-[12px]",
!icon && (size === "md" ? "pl-2.5" : "pl-2"),
outline ? cn("bg-page shadow-[inset_0_0_0_1px_currentColor]", tint[tone].fill.split(" ")[1]) : tint[tone].fill,
className,
)}
>
{icon && <Icon name={icon} size={size === "md" ? 16 : 14} className={tint[tone].icon} />}
{children}
</span>
);
}
/** Solid badge, kept for who someone is (Verified, Licence lapsed), never for a state. */
export function SolidBadge({ tone, icon, children }: { tone: "red" | "green" | "grey" | "ink"; icon?: IconName; children: ReactNode }) {
const bg = { red: "bg-red-icon", green: "bg-action", grey: "bg-grey-icon", ink: "bg-ink" }[tone];
return (
<span className={cn("inline-flex h-[22px] items-center gap-1 rounded-full pr-[9px] pl-[5px] text-[12px] font-semibold text-white", !icon && "pl-[9px]", bg)}>
{icon && <Icon name={icon} size={14} />}
{children}
</span>
);
}
/** Outlined DM Mono tag for codes, units, sources and filters. */
export function Tag({ children, lead, trail, count, className }: { children: ReactNode; lead?: IconName; trail?: IconName; count?: number; className?: string }) {
return (
<span
className={cn(
"inline-flex h-6 items-center gap-[5px] rounded-md px-2 font-mono text-[12px] font-medium whitespace-nowrap text-body shadow-[inset_0_0_0_1px_var(--color-line)]",
lead && "pl-1.5",
(trail || count !== undefined) && "pr-1",
className,
)}
>
{lead && <Icon name={lead} size={14} className="text-muted" />}
{children}
{trail && <Icon name={trail} size={14} className="text-subtle" />}
{count !== undefined && <span className="ml-0.5 rounded bg-quiet px-1.5 py-px text-[11px] text-muted">{count}</span>}
</span>
);
}
/** Small count next to a section title or a nav item. */
export function Count({ children, tone = "grey", className, ...aria }: { children: ReactNode; tone?: "grey" | "red" | "ink"; className?: string } & AriaAttributes) {
const c = { grey: "bg-quiet text-muted", red: "bg-red-icon text-white", ink: "bg-grey-icon text-white" }[tone];
return (
<span className={cn("inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full px-1.5 font-mono text-[11px] leading-none font-medium", c, className)} {...aria}>
{children}
</span>
);
}
/** A small dot of colour. Decorative unless it is given a role and a label. */
export function Dot({ tone, className, role, ...aria }: { tone: Tone; className?: string; role?: "img" } & AriaAttributes) {
return <span role={role} aria-hidden={role ? undefined : true} className={cn("inline-block size-1.5 shrink-0 rounded-full", tint[tone].dot, className)} {...aria} />;
}