motion: calm admin tab/drawer/popover/toast transitions
Deploy to route.crispygoat.com / deploy (push) Successful in 4m15s

Follow-up to 9fcc514. The global motion pass hit the public site;
this targets the admin design system, which had its own animation
budget that was still producing positional movement on every
interaction.

Killed:
- TabSwitcher y:4 slide (every tab change in /admin)
- CommandPalette scale(0.98) panel pop
- ha-drawer-in: 100% translateX (full-screen sweep)
- ha-popover-in: translateY(-4px) + scale(0.98)
- ha-check-pop: cubic-bezier(0.34, 1.56, 0.64, 1) 1.15x overshoot
- slide-in-from-right: 100% translateX (toasts)
- dashboard-fade-up: 10px Y slide on every card
- tailwindcss-animate keyframes for slide-in-from-{left,right,top,bottom} and zoom-in-{90,95} (used by ToastContainer, LotDetailPanel)

Calmed:
- All durations cut to 120-200ms range with ease-out
- All keyframes that survived are opacity-only

Added a @media (prefers-reduced-motion: reduce) block scoped to
the admin selectors so users with that OS setting see instant
transitions across toasts/drawers/popovers/dashboard cards.
This commit is contained in:
Tyler
2026-06-17 08:48:17 -06:00
parent 9fcc514045
commit 36e13ba7fa
3 changed files with 107 additions and 23 deletions
+14 -6
View File
@@ -10,18 +10,26 @@ type Props = {
/**
* Fades the content of a tabbed panel when the active tab changes.
* The tab key drives the animation, so switching tabs triggers a quick
* fade-in of the new content. No motion when the tab key is stable
* (i.e. on first render of a given tab).
* opacity-only fade-in of the new content.
*
* Motion note: the previous version used `y: 4` / `y: -4` on enter/exit
* for a small slide. That 4px slide, multiplied across every tab change
* in the admin, was a steady source of positional movement. Now it's
* pure opacity, 120ms, ease-out. `reducedMotion="user"` on the global
* <MotionConfig> will further strip this to instant if the user has
* prefers-reduced-motion enabled.
*
* No motion when the tab key is stable (i.e. on first render of a given tab).
*/
export function TabSwitcher({ tabKey, children }: Props) {
return (
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={tabKey}
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.18, ease: [0.16, 1, 0.3, 1] }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.12, ease: "easeOut" }}
>
{children}
</motion.div>