Clarify UI status and remove noisy live regions

This commit is contained in:
2026-10-07 18:47:02 -04:00
parent 45e9bcd4ce
commit e35b84475d
12 changed files with 172 additions and 108 deletions
@@ -1,66 +1,40 @@
<script lang="ts">
import type { Health } from '$ui/stores/servers.js';
import { statusPresentation, statusShapes, type StatusTone } from '$ui/status.js';
interface Props {
health?: Health;
state?: string;
tone?: StatusTone;
size?: 'sm' | 'md';
label?: string | null;
}
let { health = 'offline', size = 'md', label = null }: Props = $props();
let toneClass = $derived(
health === 'online' ? 't-online' :
health === 'stale' || health === 'starting' || health === 'provisioning' ? 't-warning' :
't-danger'
);
let announceLabel = $derived(
label ?? (
health === 'online' ? 'online' :
health === 'starting' ? 'starting' :
health === 'provisioning' ? 'provisioning' :
health === 'stale' ? 'stale' :
'offline'
)
);
let { health = 'offline', state, tone, size = 'md', label = null }: Props = $props();
const presentation = $derived(statusPresentation(state ?? health));
const resolvedTone = $derived(tone ?? presentation.tone);
const shape = $derived(statusShapes[resolvedTone]);
</script>
<span class="pip s-{size} {toneClass}" role="status" aria-label={announceLabel}>
<span class="halo" aria-hidden="true"></span>
<span class="dot" aria-hidden="true"></span>
</span>
<span class="pip s-{size} t-{resolvedTone} shape-{shape}" role={label ? 'img' : undefined} aria-label={label ?? undefined} aria-hidden={label ? undefined : true}></span>
<style>
.pip {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-sizing: border-box;
}
.s-sm { width: 8px; height: 8px; }
.s-md { width: 10px; height: 10px; }
.dot {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
border-radius: 50%;
}
/* Halo — opacity+scale only, GPU-cheap, no box-shadow ring. */
.halo {
position: absolute;
inset: 0;
border-radius: 50%;
opacity: 0;
}
.t-online .dot { background: var(--state-online); box-shadow: 0 0 4px rgba(124, 212, 255, 0.55); }
.t-online .halo { background: var(--state-online); animation: pip-breathe 2.4s var(--ease-standard) infinite; }
.t-warning .dot { background: var(--state-warning); }
.t-danger .dot { background: var(--state-danger); opacity: 0.7; }
.s-sm { width: 10px; height: 10px; }
.s-md { width: 12px; height: 12px; }
.t-online { color: var(--state-online); }
.t-warning { color: var(--state-warning); }
.t-danger { color: var(--state-danger); }
.t-info { color: var(--state-info); }
.t-neutral { color: var(--state-neutral); }
.shape-circle { border-radius: 50%; background: currentColor; }
.shape-square { border-radius: 2px; background: currentColor; }
.shape-triangle { background: currentColor; clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.shape-hollow, .shape-ring { border: 2px solid currentColor; border-radius: 50%; }
.shape-ring { border-right-color: transparent; animation: status-spin 1s linear infinite; }
@keyframes status-spin { to { transform: rotate(360deg); } }
</style>