feat(monitor/frontend): alert banner (one-amber-fill overlay, 30s auto-dismiss)

The only surface in the dashboard that uses amber as a fill color — the
one-alarm-color rule reserves this paint for "look here." 48px strip in
the alertbanner grid-area, dark text (--amber-fg #1a1a1a) on the amber
fill for cross-room legibility, ellipsis on long messages, dismiss button
at right. Returns null when the ui store has no currentAlert so the row
collapses to zero and the center grid sits flush with the top strip.

Auto-dismiss after 30s via setTimeout in useEffect; cleanup on unmount
or alert change. role="alert" for screen readers. focus-visible outline
1px --amber-fg.

Adds --amber-fg to ethos tokens — pairs explicitly with --amber so any
future amber-fill surface uses the same dark text. Also cleans up an
empty `//` line in the ethos comment block (stylelint scss/comment-no-empty).
This commit is contained in:
CarterPerez-dev 2026-05-03 06:58:37 -04:00
parent 5e33a4db22
commit 0ba29226a2
4 changed files with 86 additions and 2 deletions

View File

@ -0,0 +1,44 @@
// ©AngelaMos | 2026
// AlertBanner.module.scss
.banner {
grid-area: alertbanner;
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
padding: 0 16px;
background: var(--amber);
color: var(--amber-fg);
font-size: var(--type-body);
font-weight: 500;
}
.message {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dismiss {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
width: 24px;
background: transparent;
border: 0;
padding: 0;
color: var(--amber-fg);
cursor: pointer;
&:hover {
color: #000;
}
&:focus-visible {
outline: 1px solid var(--amber-fg);
outline-offset: 1px;
}
}

View File

@ -0,0 +1,38 @@
// ©AngelaMos | 2026
// AlertBanner.tsx
import { useEffect } from 'react'
import { FiX } from 'react-icons/fi'
import { useUIStore } from '@/stores/ui'
import styles from './AlertBanner.module.scss'
const AUTO_DISMISS_MS = 30_000
export function AlertBanner(): React.ReactElement | null {
const alert = useUIStore((s) => s.currentAlert)
const dismiss = useUIStore((s) => s.dismissAlert)
useEffect(() => {
if (!alert) return
const id = setTimeout(dismiss, AUTO_DISMISS_MS)
return () => clearTimeout(id)
}, [alert, dismiss])
if (!alert) return null
return (
<aside className={styles.banner} role="alert">
<span className={styles.message}>{alert.message}</span>
<button
type="button"
className={styles.dismiss}
onClick={dismiss}
aria-label="Dismiss"
>
<FiX aria-hidden />
</button>
</aside>
)
}
AlertBanner.displayName = 'AlertBanner'

View File

@ -1,6 +1,7 @@
// ©AngelaMos | 2026
// Dashboard.tsx
import { AlertBanner } from './AlertBanner'
import styles from './Dashboard.module.scss'
import { TopStrip } from './TopStrip'
@ -8,6 +9,7 @@ export function Dashboard(): React.ReactElement {
return (
<div className={styles.root}>
<TopStrip />
<AlertBanner />
<main className={styles.grid}>
<aside className={styles.left} />
<section className={styles.center} />

View File

@ -168,8 +168,7 @@ $container-2xl: 42rem;
$container-full: 100%;
// ============================================================================
// ETHOS TOKENS operator-grade dashboard surface
//
// ETHOS TOKENS operator-grade dashboard surface.
// Per docs/design/ethos-reference.md (also mirrored in _ethos.scss).
// Used by /pages/dashboard/, /pages/globe/, /pages/panels/. NOT used by
// auth pages those keep the template's SCSS variables above.
@ -192,6 +191,7 @@ $container-full: 100%;
// Accents ONE green, ONE amber. That is all.
--ok: #4ade80; // live / healthy / busy
--amber: #f59e0b; // stale / breached / anomaly. NEVER for decoration.
--amber-fg: #1a1a1a; // near-black text-on-amber pairing (alert banner only)
// Type
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;