Files
notify-bridge/frontend/src/lib/components/Hint.svelte
T
alexei.dolgolyov 711f218622 fix(redesign): a11y, mobile, perf polish for production push
Comprehensive pre-production sweep across the Aurora redesign — drives
svelte-check to 0 errors / 0 warnings (was 61) without changing visual
intent. Highlights:

- Mobile: hero title shrinks at 480px, signal-list stacks timestamp
  under sentence below 640px, sidebar icon buttons bumped to 40x40
- Light theme: muted-foreground darkened to #3a3560 to clear WCAG AA
  on glass surfaces and the modal close button
- Perf: topbar backdrop-filter 28→14px, mobile-more sheet 28→12px to
  cut concurrent blur layers on mid-tier mobile
- a11y: prefers-reduced-motion mute for aurora drift / pulses /
  shimmer / stagger; aria-label on every icon-only button;
  aria-describedby on Hint; combobox/listbox/aria-activedescendant on
  SearchPalette; modal dialog tabindex; 47 label-without-control
  warnings across 14 form pages cleaned up via for=/id= or label→div
- Dashboard derived state split into topology- vs status-bound layers
  so polling no longer re-runs the full provider/wires computation
- Mobile bottom nav derived from baseNavEntries by key lookup so
  adding a top-level nav entry keeps the two trees in sync
- Bug: template-configs page now respects the global provider filter
  for both the count meter and the type pill (was reading the
  unfiltered cache)
- Misc: portal EventChart tooltip and switch its swatches to Aurora
  tokens; CollapsibleSlot warning state uses warning-fg/-bg tokens
  instead of #d97706; Hint z-index 99999→9999; element refs across
  Modal/EntitySelect/MultiEntitySelect/SearchPalette/IconGridSelect/
  Hint/targets converted to \$state for reactivity; 4 dead
  .topbar-cta selectors removed
2026-04-25 14:41:12 +03:00

73 lines
2.2 KiB
Svelte

<script lang="ts">
import { portal } from '$lib/portal';
let { text = '' } = $props<{ text: string }>();
let visible = $state(false);
let tooltipStyle = $state('');
let btnEl = $state<HTMLButtonElement | undefined>();
const tooltipId = `hint-${Math.random().toString(36).slice(2, 9)}`;
function show() {
if (!btnEl) return;
visible = true;
const rect = btnEl.getBoundingClientRect();
const tooltipWidth = 272;
let left = rect.left + rect.width / 2 - tooltipWidth / 2;
if (left < 8) left = 8;
if (left + tooltipWidth > window.innerWidth - 8) left = window.innerWidth - tooltipWidth - 8;
tooltipStyle = `position:fixed; z-index:9999; bottom:${window.innerHeight - rect.top + 8}px; left:${left}px; width:${tooltipWidth}px;`;
}
function hide() {
visible = false;
}
</script>
<button type="button" bind:this={btnEl}
class="hint-btn inline-flex items-center justify-center w-4 h-4 rounded-full text-[11px] font-bold leading-none
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary)]
transition-colors cursor-help align-middle ml-2 flex-shrink-0"
onmouseenter={show}
onmouseleave={hide}
onfocus={show}
onblur={hide}
aria-label={text}
aria-describedby={visible ? tooltipId : undefined}
title={text}
tabindex="0"
>?</button>
{#if visible}
<div use:portal>
<div id={tooltipId} role="tooltip" style={tooltipStyle} class="hint-tooltip">
{text}
</div>
</div>
{/if}
<style>
.hint-btn {
border: 1px solid var(--color-border);
background: var(--color-glass-strong);
color: var(--color-muted-foreground);
}
.hint-btn:hover {
background: var(--color-glass-elev);
color: var(--color-foreground);
border-color: var(--color-rule-strong);
}
.hint-tooltip {
background: var(--hint-solid-bg, #131520);
color: var(--color-foreground);
border: 1px solid var(--color-rule-strong);
box-shadow: var(--shadow-card), 0 12px 30px -10px rgba(0, 0, 0, 0.5);
padding: 0.7rem 0.85rem;
border-radius: 12px;
font-size: 0.8125rem;
white-space: normal;
line-height: 1.55;
pointer-events: none;
}
:global([data-theme="light"]) .hint-tooltip { --hint-solid-bg: #fafafe; }
</style>