add profiles tab to settings

This commit is contained in:
Eduard Gert
2026-05-20 13:17:13 +02:00
parent b79b62bee4
commit 1c15e9976b
12 changed files with 450 additions and 36 deletions
@@ -0,0 +1,64 @@
import { forwardRef, type ComponentType, type HTMLAttributes } from "react";
import type { LucideProps } from "lucide-react";
import { cn } from "@/lib/cn";
export type BadgeVariant =
| "info"
| "neutral"
| "brand"
| "success"
| "warning"
| "danger";
type Props = HTMLAttributes<HTMLSpanElement> & {
/** Visual color scheme. Defaults to `info` (sky), used as the
* "Active profile" indicator. */
variant?: BadgeVariant;
/** Optional leading lucide icon. */
icon?: ComponentType<LucideProps>;
/** Override icon size. Defaults to 10px to match the compact pill. */
iconSize?: number;
};
const VARIANT_CLASSES: Record<BadgeVariant, string> = {
info: "bg-sky-900 border border-sky-700 text-sky-200",
neutral: "bg-nb-gray-900 border border-nb-gray-850 text-nb-gray-200",
brand: "bg-netbird/15 border border-netbird/30 text-netbird",
success: "bg-green-900 border border-green-700 text-green-200",
warning: "bg-yellow-900 border border-yellow-700 text-yellow-200",
danger: "bg-red-900 border border-red-700 text-red-200",
};
// Pill shape sized for inline use next to text. `top-px` nudges the badge
// down so its midline aligns with the surrounding text baseline; `leading-none`
// lets the small text sit flush in the pill without the line-height padding
// inflating it.
export const Badge = forwardRef<HTMLSpanElement, Props>(function Badge(
{
variant = "info",
icon: Icon,
iconSize = 10,
className,
children,
...rest
},
ref,
) {
return (
<span
ref={ref}
className={cn(
"relative top-px inline-flex items-center gap-1 rounded-full px-2 py-[0.2rem]",
"text-[0.65rem] leading-none font-semibold shrink-0",
VARIANT_CLASSES[variant],
className,
)}
{...rest}
>
{Icon && <Icon size={iconSize} />}
{children}
</span>
);
});
export default Badge;
@@ -1,15 +1,17 @@
import { ButtonHTMLAttributes, forwardRef } from "react";
import {
Beaker,
Briefcase,
Building,
Cloud,
Construction,
FlaskConical,
Gamepad2,
GraduationCap,
House,
Cloud,
ServerCog,
Radio,
Server,
SquareCode,
TestTube,
Terminal,
UserCircle,
UserPlus,
Users,
@@ -17,17 +19,21 @@ import {
} from "lucide-react";
import { cn } from "@/lib/cn";
// Patterns match substrings, case-insensitive — "Proxytest" hits FlaskConical
// just like "test" does. The list is scanned in order, so more-specific
// tokens (e.g. "staging" before "stage") should come first when they share
// roots.
const ICON_MAP: ReadonlyArray<[RegExp, LucideIcon]> = [
[/(default|personal)/i, UserCircle],
[/(work|business|office|company|corp|corporate)/i, Briefcase],
[/(home|house|private)/i, House],
[/(dev|development|developer|code|coding|engineering)/i, SquareCode],
[/(local|localhost|loopback)/i, SquareCode],
[/(stage|staging)/i, Beaker],
[/(test|testing|qa)/i, TestTube],
[/(local|localhost|loopback)/i, Terminal],
[/(stage|staging|preprod|pre-prod)/i, Construction],
[/(test|testing|qa)/i, FlaskConical],
[/(prod|production)/i, Cloud],
[/(live)/i, Cloud],
[/(selfhosted|self-hosted|on-prem|onprem)/i, ServerCog],
[/(live)/i, Radio],
[/(selfhosted|self-hosted|on-prem|onprem)/i, Server],
[/(school|university|edu|study|student)/i, GraduationCap],
[/(client|customer)/i, Building],
[/(family)/i, Users],