swarm-ui: animate the three header popovers on open

Part of #3591 (mara: "pop ups and menus appearing should animate").
Each popover mounts fresh on open ({open ? <div> : null}, not a state
transition), so a keyframe animation on the popover element itself is
the right tool -- same shape as Shell.css's own shell-page-enter
(fade + a slight translate/scale settle), including the identical
three-rule motion-guard (base rule, prefers-reduced-motion media
query, data-motion=reduce/allow explicit overrides).

Scope: LinksMenu, SettingsMenu, UserMenu -- the three header popovers.
Not included here (posted findings on the issue instead of guessing):
the refresh-interval picker's dropdown (native <select>, whose open
popup is OS/browser chrome outside CSS reach in current browsers --
"not themed" is a platform limitation, not a bug in this component's
own styling) and the jobs graph's node animations (JobqGraph is a
@hive/shared component consumed by both swarm-ui and the per-hive
dashboard, real design/implementation work on shared infra, not a
same-shape mechanical extension of an existing pattern).

Screenshot-verified the settled (post-animation) state renders
correctly; a static screenshot cannot show an in-flight CSS animation,
so this leans on exact structural parity with the already-shipped
Shell.css pattern for the animation's own correctness.
This commit is contained in:
iris 2026-08-24 15:46:44 +02:00 committed by mara
commit 2c8d37d5b8
3 changed files with 77 additions and 0 deletions

View file

@ -43,6 +43,33 @@
border-radius: 0.5em;
background: var(--bg-elev);
box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.3);
animation: links-menu-popover-enter 140ms ease;
}
/* Mount-triggered, not a state transition the popover only exists in
the DOM while open (`{open ? <div> : null}`), so a keyframe animation
on entry is the right tool, same shape as `.shell-body`'s own
`shell-page-enter` (Shell.css) that's the precedent every popover
in this issue's "menus should animate" ask follows. */
@keyframes links-menu-popover-enter {
from {
opacity: 0;
transform: translateY(-0.25em) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
.links-menu-popover {
animation: none;
}
}
:root[data-motion='reduce'] .links-menu-popover {
animation: none;
}
:root[data-motion='allow'] .links-menu-popover {
animation: links-menu-popover-enter 140ms ease;
}
.links-menu-item {
display: flex;

View file

@ -40,6 +40,31 @@
border-radius: 0.5em;
background: var(--bg-elev);
box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.3);
animation: settings-menu-popover-enter 140ms ease;
}
/* Mount-triggered, same shape as `LinksMenu.css`'s own version see
its comment for why a keyframe (not a transition) is the right tool
here. */
@keyframes settings-menu-popover-enter {
from {
opacity: 0;
transform: translateY(-0.25em) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
.settings-menu-popover {
animation: none;
}
}
:root[data-motion='reduce'] .settings-menu-popover {
animation: none;
}
:root[data-motion='allow'] .settings-menu-popover {
animation: settings-menu-popover-enter 140ms ease;
}
.settings-menu-row {
display: flex;

View file

@ -41,6 +41,31 @@
border-radius: 0.5em;
background: var(--bg-elev);
box-shadow: 0 0.25em 0.75em rgba(0, 0, 0, 0.3);
animation: user-menu-popover-enter 140ms ease;
}
/* Mount-triggered, same shape as `LinksMenu.css`'s own version see
its comment for why a keyframe (not a transition) is the right tool
here. */
@keyframes user-menu-popover-enter {
from {
opacity: 0;
transform: translateY(-0.25em) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
.user-menu-popover {
animation: none;
}
}
:root[data-motion='reduce'] .user-menu-popover {
animation: none;
}
:root[data-motion='allow'] .user-menu-popover {
animation: user-menu-popover-enter 140ms ease;
}
.user-menu-name {
padding: 0.4em 0.6em 0.5em;