67 lines
1.9 KiB
Svelte
67 lines
1.9 KiB
Svelte
<script lang="ts">
|
|
import type { Health } from '$ui/stores/servers.js';
|
|
|
|
interface Props {
|
|
health?: Health;
|
|
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'
|
|
)
|
|
);
|
|
</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>
|
|
|
|
<style>
|
|
.pip {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.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; }
|
|
</style>
|