feat(web): add product-wide UI animations (#3294)

* feat(web): add product-wide UI animations

Add motion library (Framer Motion) for modal/toast/popover exit
animations via AnimatePresence, and a comprehensive CSS entrance
animation system (entrance.css) for all other UI surfaces.

Motion is limited to low-frequency mount/unmount components (modals,
toasts, popovers) to avoid re-render overhead. All high-frequency
components (grids, lists, nav, inputs) use pure CSS @keyframes with
staggered nth-child delays — zero JS cost.

Covers: home hero sequence, card grids, design kanban, marketplace,
settings, memory panels, MCP picker, inspector/comment slide-in,
chat history, file viewer, connector drawer, error banners, agent
cards, automation history, skill rows, breadcrumbs, workspace tabs,
staged attachments, code viewer, and button press feedback.

Respects prefers-reduced-motion system preference.

* fix(web): prevent thumbnail flash on tab switch

Replace individual card/row stagger animations (opacity 0→1 per child)
with container-level fades. The child animations replayed on every
React remount when switching tabs, causing all preview thumbnails to
flash from invisible.

Now only containers animate (fast fade-in), individual items inside
grids/lists stay fully visible immediately.

* fix: remove fade animations from tab-switchable content to prevent flash on remount

Grids, tab panels, entry sections, viewer panels, and other content that
remounts on tab switch no longer start from opacity:0. Only true one-time
mounts (page load, home hero), overlays/popovers, slide-in panels, and
error banners retain entrance animations.

* fix: keep tab content mounted with display:none to prevent thumbnail reload on tab switch

Projects, Tasks, Plugins, and Design Systems tabs now stay mounted in the
DOM when inactive, hidden via display:none. This preserves loaded images
and prevents the visible flash caused by thumbnails re-fetching on every
tab switch. Home and Integrations tabs keep conditional rendering since
they have no persistent media to preserve.

* fix: keep HomeView mounted to preserve recent project thumbnails on tab switch

* perf: use content-visibility:hidden for inactive tabs + add card press feedback

Replace display:none with content-visibility:hidden so the browser skips
rendering computation for hidden tabs while preserving the full DOM tree.
Add :active scale(0.98) press feedback to design-card, plugins-home__card,
and recent-projects__card for tactile click response.

* fix: pin dependency versions to exact (no caret ranges)

* chore: update lockfile for pinned dependency versions

* fix: replace PreviewModal motion with CSS animation, add motion test mock

- PreviewModal no longer uses motion/react — prevents test failures from
  AnimatePresence exit animations never completing in test env
- Add CSS animations for .ds-modal-backdrop (fade-in) and .ds-modal (scale-in)
- Add vitest alias to mock motion/react so AnimatePresence in other
  components (UpdaterPopup, ExamplesTab) completes synchronously in tests

* chore(nix): refresh pnpm deps hash

* test(e2e): scope visual-home locators to their entry view

The redesigned entry shell keeps every view mounted (only the active one
is visible) so tab switches don't reload thumbnails. That makes testids
like `plugins-home-section` and text like "Launchpad dashboard" exist in
more than one view at once, breaking Playwright strict-mode locators.
Tag each view container with `data-testid="entry-view-<name>"` and scope
the affected visual specs to the relevant view so the locators stay
unambiguous.

* test(e2e): scope entry-chrome home starters locators to their view

The redesigned entry shell keeps every view mounted (only the active one
is visible), so `plugins-home-section` and its children render in both the
home and plugins views at once, breaking Playwright strict-mode locators.
Scope the home starters assertions in entry-chrome-flows to the
`entry-view-home` container so the locators stay unambiguous, matching the
existing visual-home spec fix.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* fix(web): close a11y/reduced-motion/exit gaps in animation layer

Address three review findings on the product-wide animation PR:

- EntryShell kept every tab view mounted via content-visibility:hidden,
  but that only skips paint — inactive Home/Projects/Plugins subtrees
  stayed in the tab order and accessibility tree. Mark inactive view
  wrappers inert + aria-hidden so keyboard users and screen readers only
  see the active view while the cached DOM survives.
- The motion/react variants animated unconditionally; the CSS
  prefers-reduced-motion block did not cover them. Wrap the app root in
  MotionConfig reducedMotion="user" so dialogs/toasts/popovers honor the
  OS preference, and add a focused regression test for the wiring.
- NewProjectModal short-circuited with `if (!open) return null`, so its
  exit variants never ran. Render the body inside AnimatePresence gated
  on `open` so the close animation plays before unmount.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
This commit is contained in:
elihahah666
2026-06-03 21:20:18 +08:00
committed by GitHub
parent 4aea5fdb01
commit 2471996f26
33 changed files with 1373 additions and 282 deletions

View File

@@ -10,5 +10,5 @@
# 2. Run the relevant nix build/flake check
# 3. Copy the expected hash printed by Nix into the matching field below
daemonHash = "sha256-I2NzPscFPwgyocWyCUKfruqEAojzB6SuI4U/klrfdCI=";
webHash = "sha256-FeCqszNQooPNx9zbKeJc30CYxfUanZ6pAO2FwnUI/Lk=";
webHash = "sha256-HJElsw92u9Q0L22m8susnyRVtUnw/yfgnV98+NltmPg=";
}