Files
webmail/components/admin/badge.tsx
T
Linus Rath 10b4d2a9db feat: rework the admin panel around a settings-style sidebar and one URL per page
The old panel kept its active tab in a store, so every page sat at
/admin, the sidebar duplicated the webmail rail by hand, and each page
was a stack of boxed cards with their own save buttons. Plugin settings
were only reachable through a gear icon in a row of five.

- one catch-all route for the whole panel; pages switch through
  history.pushState, so `/admin/plugins/<id>`, the back button, bookmarks
  and connector links all work without a route transition
- sidebar laid out like the user settings: back to mail, search over
  pages and their sections, two group headings, change password and
  sign out at the bottom; the phone shows a list, then the page
- pages use SettingsSection rows instead of nested cards, with one page
  header, one flash message and one save bar at the bottom (Discard,
  Save, Ctrl+S); the unsaved-changes prompt still guards leaving
- plugins and themes are lists of rows with a toggle each; a row opens
  the extension's own page with its settings, force-on, permissions,
  kept versions and removal
- overview shows stat tiles, server facts and recent activity
- old `/admin?tab=x` links redirect; the tab store is gone and the
  connector registry resolves admin links to the page URL
- `NEXT_TURBOPACK_ROOT` overrides the Turbopack root so `next dev` runs
  from a worktree with a junctioned node_modules
2026-10-20 15:16:05 +02:00

35 lines
1.1 KiB
TypeScript

import type { ReactNode } from 'react';
import { cn } from '@/lib/utils';
export type BadgeTone = 'success' | 'warning' | 'danger' | 'info' | 'muted' | 'accent';
const TONES: Record<BadgeTone, string> = {
success: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-400',
warning: 'bg-amber-100 text-amber-700 dark:bg-amber-950/30 dark:text-amber-400',
danger: 'bg-destructive/10 text-destructive',
info: 'bg-blue-100 text-blue-700 dark:bg-blue-950/30 dark:text-blue-400',
muted: 'bg-muted text-muted-foreground',
accent: 'bg-primary/10 text-primary',
};
interface BadgeProps {
tone?: BadgeTone;
icon?: ReactNode;
children: ReactNode;
title?: string;
className?: string;
}
/** A small label next to a name: a state, a source, a count. */
export function Badge({ tone = 'muted', icon, children, title, className }: BadgeProps) {
return (
<span
title={title}
className={cn('inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-medium leading-4 whitespace-nowrap', TONES[tone], className)}
>
{icon}
{children}
</span>
);
}