Clarify UI status and remove noisy live regions
This commit is contained in:
@@ -63,9 +63,9 @@
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
.v-online {
|
||||
background: rgba(124, 212, 255, 0.10);
|
||||
background: color-mix(in srgb, var(--state-online) 10%, transparent);
|
||||
color: var(--state-online);
|
||||
border-color: rgba(124, 212, 255, 0.25);
|
||||
border-color: color-mix(in srgb, var(--state-online) 25%, transparent);
|
||||
}
|
||||
.v-warning {
|
||||
background: rgba(255, 176, 32, 0.10);
|
||||
@@ -78,9 +78,9 @@
|
||||
border-color: rgba(255, 86, 112, 0.28);
|
||||
}
|
||||
.v-info {
|
||||
background: rgba(124, 192, 245, 0.10);
|
||||
color: var(--blue-300);
|
||||
border-color: rgba(124, 192, 245, 0.28);
|
||||
background: color-mix(in srgb, var(--state-info) 10%, transparent);
|
||||
color: var(--state-info);
|
||||
border-color: color-mix(in srgb, var(--state-info) 28%, transparent);
|
||||
}
|
||||
.v-accent {
|
||||
background: var(--accent-primary);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
href?: string | null;
|
||||
as?: 'div' | 'section' | 'article' | 'a';
|
||||
ariaLabel?: string;
|
||||
ariaLabelledby?: string;
|
||||
class?: string;
|
||||
style?: string;
|
||||
children?: Snippet;
|
||||
@@ -22,6 +23,7 @@
|
||||
href = null,
|
||||
as = 'div',
|
||||
ariaLabel,
|
||||
ariaLabelledby,
|
||||
class: klass = '',
|
||||
style,
|
||||
children,
|
||||
@@ -41,6 +43,7 @@
|
||||
class="card p-{padding} e-{elevation} {klass}"
|
||||
href={isLink ? href : undefined}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledby}
|
||||
{role}
|
||||
{tabindex}
|
||||
{style}
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
icon?: Snippet;
|
||||
actions?: Snippet;
|
||||
spacious?: boolean;
|
||||
headingLevel?: 2 | 3 | 4;
|
||||
}
|
||||
|
||||
let { title, description = null, icon, actions, spacious = false }: Props = $props();
|
||||
let { title, description = null, icon, actions, spacious = false, headingLevel = 2 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="empty" class:spacious role="status">
|
||||
<div class="empty" class:spacious>
|
||||
{#if icon}
|
||||
<div class="icon">{@render icon()}</div>
|
||||
{/if}
|
||||
<h3 class="title">{title}</h3>
|
||||
<svelte:element this={`h${headingLevel}`} class="title">{title}</svelte:element>
|
||||
{#if description}<p class="desc">{description}</p>{/if}
|
||||
{#if actions}<div class="actions">{@render actions()}</div>{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Badge from './Badge.svelte';
|
||||
import StatusPip from './StatusPip.svelte';
|
||||
import { statusPresentation } from '../../status.js';
|
||||
let { state, size = 'sm' }: { state: string; size?: 'sm' | 'md' } = $props();
|
||||
const status = $derived(statusPresentation(state));
|
||||
</script>
|
||||
<Badge variant={status.tone} {size}>{status.label}</Badge>
|
||||
<Badge variant={status.tone} {size}><StatusPip {state} size="sm" />{status.label}</Badge>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user