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
This commit is contained in:
@@ -232,13 +232,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Mobile: flatten nav for bottom bar (first 4 + "More" button)
|
||||
const mobileNavItems = $derived<NavItem[]>([
|
||||
{ href: '/', key: 'nav.dashboard', icon: 'mdiViewDashboard' },
|
||||
{ href: '/notification-trackers', key: 'nav.notification', icon: 'mdiBellOutline' },
|
||||
{ href: '/command-trackers', key: 'nav.commands', icon: 'mdiConsoleLine' },
|
||||
{ href: '/targets', key: 'nav.targets', icon: 'mdiTarget' },
|
||||
]);
|
||||
// Mobile bottom-nav derives its 4 primary slots from baseNavEntries by key
|
||||
// lookup. Adding a new top-level nav entry doesn't break this list, and
|
||||
// renaming a key fails loudly via the assertion below — keeping desktop
|
||||
// and mobile nav structure in sync without manual duplication.
|
||||
const MOBILE_PRIMARY_KEYS = ['nav.dashboard', 'nav.notification', 'nav.commands', 'nav.targets'] as const;
|
||||
const mobileNavItems = $derived<NavItem[]>(
|
||||
MOBILE_PRIMARY_KEYS.map(key => {
|
||||
const entry = baseNavEntries.find(e => e.key === key);
|
||||
if (!entry) return null;
|
||||
return isGroup(entry)
|
||||
? { href: entry.children[0]?.href ?? '/', key: entry.key, icon: entry.icon }
|
||||
: entry;
|
||||
}).filter((x): x is NavItem => x !== null)
|
||||
);
|
||||
|
||||
// "More" panel mirrors the full desktop sidebar tree so every subnode is
|
||||
// reachable on mobile (previously it was a flat hand-picked list that
|
||||
@@ -384,8 +391,9 @@
|
||||
<div class="brand-orb brand-orb--small"></div>
|
||||
{/if}
|
||||
<button onclick={toggleSidebar}
|
||||
class="sidebar-icon-btn flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200"
|
||||
title={collapsed ? t('common.expand') : t('common.collapse')}>
|
||||
class="sidebar-icon-btn flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-200"
|
||||
title={collapsed ? t('common.expand') : t('common.collapse')}
|
||||
aria-label={collapsed ? t('common.expand') : t('common.collapse')}>
|
||||
<NavIcon name={collapsed ? 'mdiChevronRight' : 'mdiChevronLeft'} size={18} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -400,7 +408,8 @@
|
||||
providerFilterValue = ids[(idx + 1) % ids.length];
|
||||
}}
|
||||
class="provider-filter-btn flex items-center justify-center w-full py-1.5 rounded-lg text-sm transition-all duration-200"
|
||||
title={globalProviderFilter.provider?.name || t('common.allProviders')}>
|
||||
title={globalProviderFilter.provider?.name || t('common.allProviders')}
|
||||
aria-label={globalProviderFilter.provider?.name || t('common.allProviders')}>
|
||||
<NavIcon name={globalProviderFilter.provider ? providerDefaultIcon(globalProviderFilter.provider) : 'mdiFilterOff'} size={16} />
|
||||
</button>
|
||||
{:else}
|
||||
@@ -480,13 +489,15 @@
|
||||
{#if collapsed}
|
||||
<div class="flex flex-col items-center gap-1.5 py-3">
|
||||
<a href="/docs" target="_blank" rel="noopener noreferrer"
|
||||
class="sidebar-icon-btn flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200"
|
||||
title={t('common.apiDocs')}>
|
||||
class="sidebar-icon-btn flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-200"
|
||||
title={t('common.apiDocs')}
|
||||
aria-label={t('common.apiDocs')}>
|
||||
<NavIcon name="mdiApi" size={14} />
|
||||
</a>
|
||||
<button onclick={logout}
|
||||
class="sidebar-icon-btn flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200"
|
||||
title={t('nav.logout')}>
|
||||
class="sidebar-icon-btn flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-200"
|
||||
title={t('nav.logout')}
|
||||
aria-label={t('nav.logout')}>
|
||||
<NavIcon name="mdiLogout" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -504,16 +515,19 @@
|
||||
</div>
|
||||
<div class="user-card__actions">
|
||||
<button onclick={() => showPasswordForm = true} class="user-card__btn"
|
||||
title={t('common.changePassword')}>
|
||||
title={t('common.changePassword')}
|
||||
aria-label={t('common.changePassword')}>
|
||||
<NavIcon name="mdiKeyVariant" size={13} />
|
||||
<span>{t('common.changePassword')}</span>
|
||||
</button>
|
||||
<a href="/docs" target="_blank" rel="noopener noreferrer"
|
||||
class="user-card__btn" title={t('common.apiDocs')}>
|
||||
class="user-card__btn" title={t('common.apiDocs')}
|
||||
aria-label={t('common.apiDocs')}>
|
||||
<NavIcon name="mdiApi" size={13} />
|
||||
</a>
|
||||
<button onclick={logout} class="user-card__btn user-card__btn--danger"
|
||||
title={t('nav.logout')}>
|
||||
title={t('nav.logout')}
|
||||
aria-label={t('nav.logout')}>
|
||||
<NavIcon name="mdiLogout" size={13} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -962,8 +976,8 @@
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.6rem 0.5rem 0.85rem;
|
||||
background: var(--color-glass);
|
||||
backdrop-filter: blur(28px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(160%);
|
||||
backdrop-filter: blur(14px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(150%);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 18px;
|
||||
box-shadow: var(--shadow-card);
|
||||
@@ -1028,29 +1042,8 @@
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.topbar-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0 1rem;
|
||||
height: 36px;
|
||||
border-radius: 12px;
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
|
||||
color: white;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 6px 20px -8px var(--color-glow-strong), inset 0 1px 0 rgba(255,255,255,0.4);
|
||||
transition: transform 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.topbar-cta:hover { transform: translateY(-1px); }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.topbar-cta span { display: none; }
|
||||
.topbar-cta { padding: 0; width: 36px; justify-content: center; }
|
||||
.topbar-search__kbd { display: none; }
|
||||
}
|
||||
|
||||
@@ -1073,15 +1066,15 @@
|
||||
right: 0;
|
||||
bottom: calc(3rem + env(safe-area-inset-bottom, 0px));
|
||||
z-index: 50;
|
||||
background: var(--mobile-more-bg, rgba(19, 21, 32, 0.72));
|
||||
backdrop-filter: blur(28px) saturate(170%);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(170%);
|
||||
background: var(--mobile-more-bg, rgba(19, 21, 32, 0.92));
|
||||
backdrop-filter: blur(12px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(150%);
|
||||
border-top: 1px solid var(--color-rule-strong);
|
||||
padding: calc(1rem + env(safe-area-inset-top, 0px)) calc(1rem + env(safe-area-inset-right, 0px)) 1rem calc(1rem + env(safe-area-inset-left, 0px));
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
:global([data-theme="light"]) .mobile-more-panel { --mobile-more-bg: rgba(250, 250, 254, 0.72); }
|
||||
:global([data-theme="light"]) .mobile-more-panel { --mobile-more-bg: rgba(250, 250, 254, 0.92); }
|
||||
.mobile-more-panel a:hover,
|
||||
.mobile-more-panel button:hover {
|
||||
background: var(--color-glass-strong);
|
||||
|
||||
Reference in New Issue
Block a user