feat: sprint 4 — UI polish + dark mode + workflow tightening + process hygiene #7
@@ -24,7 +24,7 @@ export function ConfirmDialog({
|
||||
aria-labelledby="confirm-dialog-title"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||||
>
|
||||
<div className="absolute inset-0 bg-ink/40" onClick={onCancel} aria-hidden="true" />
|
||||
<div className="modal-backdrop absolute inset-0" onClick={onCancel} aria-hidden="true" />
|
||||
<div className="relative card-product shadow-floating max-w-sm w-full mx-md flex flex-col gap-md">
|
||||
<h2 id="confirm-dialog-title" className="text-[20px] font-medium text-ink">
|
||||
{title}
|
||||
|
||||
@@ -28,13 +28,13 @@ export function Layout(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="min-h-full flex flex-col bg-canvas">
|
||||
{/* utility-strip — ink slab, fine print */}
|
||||
<div className="bg-ink text-white text-[14px] h-9 flex items-center">
|
||||
{/* utility-strip — fixed dark slab, never inverts */}
|
||||
<div className="bg-slab text-slab-text text-[14px] h-9 flex items-center">
|
||||
<div className="mx-auto w-full max-w-page px-xl flex items-center justify-between">
|
||||
<span className="font-medium tracking-[0.5px] uppercase">Mimic · Purple Team BAS</span>
|
||||
{user ? (
|
||||
<div className="flex items-center gap-md">
|
||||
<span className="text-[12px] uppercase tracking-[0.5px] text-steel">
|
||||
<span className="text-[12px] uppercase tracking-[0.5px] text-slab-muted">
|
||||
{user.role}
|
||||
</span>
|
||||
<span className="text-[14px]">{user.username}</span>
|
||||
@@ -42,7 +42,7 @@ export function Layout(): JSX.Element {
|
||||
type="button"
|
||||
onClick={cycleTheme}
|
||||
aria-label={`Theme: ${themeLabel(theme)} — click to cycle`}
|
||||
className="flex items-center gap-xxs text-[12px] text-steel hover:text-white transition-colors"
|
||||
className="flex items-center gap-xxs text-[12px] text-slab-muted hover:text-slab-text transition-colors"
|
||||
>
|
||||
<ThemeIcon theme={theme} />
|
||||
<span className="uppercase tracking-[0.5px]">{themeLabel(theme)}</span>
|
||||
@@ -101,9 +101,9 @@ export function Layout(): JSX.Element {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* footer — ink slab close */}
|
||||
<footer className="bg-ink text-white">
|
||||
<div className="mx-auto w-full max-w-page px-xl py-xl text-[12px] text-steel">
|
||||
{/* footer — fixed dark slab, never inverts */}
|
||||
<footer className="bg-slab text-slab-text">
|
||||
<div className="mx-auto w-full max-w-page px-xl py-xl text-[12px] text-slab-muted">
|
||||
Mimic — Internal Purple Team tooling. Authorized engagements only.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -163,14 +163,14 @@ export function MitreMatrixModal({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-ink/60" onClick={onCancel} aria-hidden="true" />
|
||||
<div className="modal-backdrop absolute inset-0" onClick={onCancel} aria-hidden="true" />
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="matrix-modal-title"
|
||||
className="relative bg-canvas rounded-xl shadow-floating max-w-[98vw] max-h-[80vh] overflow-hidden flex flex-col"
|
||||
className="relative bg-canvas rounded-xl shadow-floating dark:shadow-floating-dark max-w-[98vw] max-h-[80vh] overflow-hidden flex flex-col"
|
||||
style={{ width: '1400px' }}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
|
||||
@@ -7,12 +7,13 @@ const LABELS: Record<SimulationStatus, string> = {
|
||||
done: 'Done',
|
||||
};
|
||||
|
||||
// pending=fog, in_progress=primary-soft, review_required=bloom-coral, done=storm-deep
|
||||
// Fixed colors — badge backgrounds are decorative/semantic, not themeable.
|
||||
// text-white is hardcoded (not text-canvas) so dark mode doesn't invert it to near-black.
|
||||
const STYLES: Record<SimulationStatus, string> = {
|
||||
pending: 'bg-fog text-charcoal border border-hairline',
|
||||
in_progress: 'bg-primary-soft text-primary-deep',
|
||||
review_required: 'bg-bloom-coral text-canvas',
|
||||
done: 'bg-storm-deep text-canvas',
|
||||
review_required: 'bg-bloom-coral text-white',
|
||||
done: 'bg-storm-deep text-white',
|
||||
};
|
||||
|
||||
export function SimulationStatusBadge({ status }: { status: SimulationStatus }): JSX.Element {
|
||||
|
||||
@@ -10,7 +10,7 @@ const STYLES: Record<EngagementStatus, string> = {
|
||||
// Outlined ink for planned (neutral), filled primary for active (engagement live),
|
||||
// outlined steel for closed (muted). Stays within DESIGN.md palette.
|
||||
planned: 'bg-canvas text-ink border border-ink',
|
||||
active: 'bg-primary text-ink-on',
|
||||
active: 'bg-primary text-white',
|
||||
closed: 'bg-cloud text-graphite border border-hairline',
|
||||
};
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ export function ToastViewport(): JSX.Element {
|
||||
{toasts.map((t) => {
|
||||
const isError = t.kind === 'error';
|
||||
const isSuccess = t.kind === 'success';
|
||||
// Fixed colors: toasts don't theme (error=dark slab, success=primary blue)
|
||||
const surface = isError
|
||||
? 'bg-ink text-ink-on'
|
||||
? 'bg-slab text-slab-text'
|
||||
: isSuccess
|
||||
? 'bg-primary text-ink-on'
|
||||
? 'bg-primary text-white'
|
||||
: 'bg-canvas text-ink border border-hairline';
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -110,7 +110,11 @@ export function UsersAdminPage(): JSX.Element {
|
||||
|
||||
<section className="card-product flex flex-col gap-md">
|
||||
<h2 className="text-[20px] font-medium">Create account</h2>
|
||||
<form onSubmit={onCreate} className="grid grid-cols-1 md:grid-cols-4 gap-md items-end">
|
||||
{/*
|
||||
items-start so each cell top-aligns; the button is wrapped in a flex column
|
||||
that pushes it to align with the input row (label + 4px gap = ~22px offset).
|
||||
*/}
|
||||
<form onSubmit={onCreate} className="grid grid-cols-1 md:grid-cols-4 gap-md items-start">
|
||||
<FormField label="Username" htmlFor="new-username" required>
|
||||
<TextInput
|
||||
id="new-username"
|
||||
@@ -137,7 +141,9 @@ export function UsersAdminPage(): JSX.Element {
|
||||
options={ROLE_OPTIONS}
|
||||
/>
|
||||
</FormField>
|
||||
<div>
|
||||
{/* Button column: spacer matches label row height so input + button baselines align */}
|
||||
<div className="flex flex-col gap-xs">
|
||||
<div className="h-[22px]" aria-hidden="true" />
|
||||
<button type="submit" className="btn-primary w-full" disabled={createMutation.isPending}>
|
||||
{createMutation.isPending ? 'Creating…' : 'Create'}
|
||||
</button>
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
* DESIGN.md component recipes.
|
||||
* Buttons stay sharp (rounded-md = 4px); cards stay soft (rounded-xl = 16px).
|
||||
*/
|
||||
|
||||
.btn-primary {
|
||||
@apply inline-flex items-center justify-center gap-xs bg-primary text-white uppercase tracking-[0.7px] font-semibold text-[14px] leading-[1.4] rounded-md px-xl py-sm h-11 transition-colors;
|
||||
}
|
||||
@@ -77,11 +78,13 @@
|
||||
@apply bg-steel cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* btn-ink uses fixed dark slab so it doesn't invert in dark mode */
|
||||
.btn-ink {
|
||||
@apply inline-flex items-center justify-center gap-xs bg-ink text-white uppercase tracking-[0.7px] font-semibold text-[14px] leading-[1.4] rounded-md px-xl py-sm h-11 transition-colors;
|
||||
@apply inline-flex items-center justify-center gap-xs text-white uppercase tracking-[0.7px] font-semibold text-[14px] leading-[1.4] rounded-md px-xl py-sm h-11 transition-colors;
|
||||
background-color: #111827;
|
||||
}
|
||||
.btn-ink:hover {
|
||||
@apply bg-ink-soft;
|
||||
background-color: #1f2937;
|
||||
}
|
||||
.btn-ink:disabled {
|
||||
@apply bg-steel cursor-not-allowed;
|
||||
@@ -115,6 +118,14 @@
|
||||
.card-product {
|
||||
@apply bg-canvas rounded-xl p-xl shadow-soft-lift;
|
||||
}
|
||||
.dark .card-product {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
/* Fixed-color modal backdrop — must not use themed ink (inverts in dark mode) */
|
||||
.modal-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.badge-pill-ink {
|
||||
@apply inline-flex items-center bg-ink text-white rounded-lg px-3 py-[6px] text-[14px] leading-[1.3] font-medium;
|
||||
|
||||
@@ -34,6 +34,10 @@ const config: Config = {
|
||||
},
|
||||
charcoal: 'var(--color-charcoal)',
|
||||
graphite: 'var(--color-graphite)',
|
||||
// Fixed dark slab — never inverts in dark mode (utility strip, footer, dark bands)
|
||||
slab: '#111827',
|
||||
'slab-text': '#f9fafb',
|
||||
'slab-muted': '#6b7280',
|
||||
// Semantic / decorative — fixed (not themeable)
|
||||
bloom: {
|
||||
coral: '#ff5050',
|
||||
@@ -91,6 +95,8 @@ const config: Config = {
|
||||
boxShadow: {
|
||||
'soft-lift': '0 2px 8px rgba(26, 26, 26, 0.08)',
|
||||
floating: '0 8px 24px rgba(26, 26, 26, 0.12)',
|
||||
'soft-lift-dark': '0 2px 8px rgba(0, 0, 0, 0.32)',
|
||||
'floating-dark': '0 8px 24px rgba(0, 0, 0, 0.48)',
|
||||
},
|
||||
maxWidth: {
|
||||
page: '1366px',
|
||||
|
||||
@@ -306,7 +306,7 @@ describe('MitreMatrixModal', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const backdrop = document.querySelector('.bg-ink\\/60') as HTMLElement;
|
||||
const backdrop = document.querySelector('.modal-backdrop') as HTMLElement;
|
||||
if (backdrop) await user.click(backdrop);
|
||||
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user