import {
SimpleGrid,
Alert,
Anchor,
Badge,
Box,
Button,
Card,
Checkbox,
Divider,
Group,
Paper,
Radio,
Select,
Stack,
Switch,
Table,
Tabs,
Text,
Textarea,
TextInput,
ThemeIcon,
Title,
useMantineTheme,
} from '@mantine/core';
import { IconAlertTriangle, IconInbox } from '@tabler/icons-react';
import { SkipLink, MAIN_CONTENT_ID } from '../layout/SkipLink';
import { StatusBadge } from '../feedback/StatusBadge';
import { WaitingFor } from '../data/WaitingFor';
import { StatTile } from '../data/StatTile';
/**
* Fixed clock. The gallery is screenshotted by the visual suite, so a tile
* reading "3d" must read "3d" tomorrow too — `Date.now()` would rewrite the
* baseline every day.
*/
const GALLERY_NOW = '2026-08-21T00:00:00.000Z';
const daysAgo = (n: number) =>
new Date(Date.parse(GALLERY_NOW) - n * 86_400_000).toISOString();
/**
* Every primitive the theme controls, on one page.
*
* This is the visual-regression surface for theme work. A real feature page
* renders a handful of primitives and only their happy states; a theme change
* that breaks `Button disabled` or `TextInput error` would sail past it. This
* renders all of them, including the states nothing else shows, so a screenshot
* diff says precisely what a theme edit did.
*
* Deliberately free of data, auth and network so it cannot flake: mounted at
* `/__gallery` outside the protected routes, it needs no API and no database.
*
* Not part of the product. Excluded from the nav on purpose.
*/
const SWATCH_SHADES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as const;
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
{title}
{children}
);
}
/** A full 0–9 ramp, so a palette swap is visible shade by shade. */
function ColorRamp({ name }: { name: string }) {
return (
{name}
{SWATCH_SHADES.map((shade) => (
{shade}
))}
);
}
export function ThemeGallery() {
const theme = useMantineTheme();
const paletteNames = Object.keys(theme.colors).filter((c) =>
// The stock Mantine ramps are noise here; show the ones this app defines,
// plus gray because overriding it is the riskiest single theme change.
c.startsWith('ema') || c === 'gray',
);
return (
<>
{/* Mirrors the real app shells, so the skip-link contract is testable
without a session. */}
Theme Gallery
Visual-regression surface for theme changes. Not a product page.
Heading 1 — Maritime licensingHeading 2 — Seafarer registryHeading 3 — Vessel registrationHeading 4 — Certificate of competencyHeading 5 — Sea service record
Body text. The quick brown fox jumps over the lazy dog.
Small dimmed text, used for subtitles and helper copy.
Extra-small text, used for metadata.
{/* Mixed-script line: the case a [lang] font swap would break. */}
Amharic: አበበ ግርማ — SF-2024-0001 — የአዲስ አበባ ወደብ
{/* Ge'ez at heading size, where its taller ascenders clip first. */}
የኢትዮጵያ ማሪታይም ባለስልጣንየመርከበኞች መዝገብ — Seafarer RegistryMonospace: ET-IMO-0231 · 28,450 GT
{paletteNames.map((name) => (
))}
Card withBorder
Radius and shadow come from the theme.
Paper withBorder
Default radius applies here too.
Paper shadow=md
Shadow ramp check.
DefaultActivePendingSuspendedExpiringDraftFilledOutlineDot
A unique Seafarer ID is generated on approval.
Upload clear copies of all required documents.
The certificate number could not be verified.
Ic
Lt
Ok
OverviewTrainingMedicalOverview panel content.