From aa0e67d219899e1eec8d6cb8153ee5b0f89a4a94 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 14:19:28 -0700 Subject: [PATCH] feat(admin-web): add @bytelyst/motion reveal/stagger on dashboard (UX-5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @bytelyst/motion added workspace:* (importer-only lockfile change; --frozen-lockfile clean). - Dashboard overview only: KPI cards grid wrapped in StaggerList (from up, 50ms stagger); the Model-Usage / Recent-Users table row wrapped in Reveal. - Primitives honor prefers-reduced-motion and resolve to opacity 1, so no element is stranded transparent (no contrast/a11y regression); prefersReduced is SSR-safe. Motion is confined to the auth-gated dashboard, not the public e2e surfaces, per tracker-web's axe/opacity caution. - vitest.config: inline @bytelyst/motion + react dedupe for the render test. Tests: happy-dom asserts Reveal/StaggerList end visible and render all children. Verify: typecheck+lint+build green (123 routes); vitest 21 files / 170 tests (+2); format:check no new failures; e2e 11 passed / 80 failed (unchanged vs UX-1 baseline — environmental). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../docs/roadmaps/UX_INTEGRATION_ADMIN.md | 12 +++- dashboards/admin-web/package.json | 1 + .../admin-web/src/__tests__/motion.test.tsx | 62 +++++++++++++++++++ .../admin-web/src/app/(dashboard)/page.tsx | 14 +++-- dashboards/admin-web/vitest.config.ts | 2 +- pnpm-lock.yaml | 3 + 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 dashboards/admin-web/src/__tests__/motion.test.tsx diff --git a/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md b/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md index 9049d3fa..58bb81af 100644 --- a/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md +++ b/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md @@ -163,9 +163,17 @@ pnpm --filter @bytelyst/admin-web test:e2e # Playwright + @axe-core (no new fa regress them (additive rule). dashboard-components reads `--color-*` (admin `@theme inline`), so themes correctly. test **20 files / 168 tests** (+3, happy-dom render of error/not-found/ loading chrome); typecheck+lint+build green; format:check no new failures; e2e unchanged. -- [ ] **UX-5 — Motion:** add `@bytelyst/motion`; subtle `Reveal`/`Stagger` on the overview dashboard +- [x] **UX-5 — Motion:** add `@bytelyst/motion`; subtle `Reveal`/`Stagger` on the overview dashboard cards + key tables; respect `prefers-reduced-motion`. (Note tracker-web's lesson: do NOT apply motion to surfaces an offline axe gate scans synchronously if transient opacity trips contrast.) + — **DONE** `` · `@bytelyst/motion` added `workspace:*` (importer-only lockfile change, + `--frozen-lockfile` clean). Dashboard overview only: KPI cards grid → `StaggerList` + (from="up", 50ms), bottom Model-Usage/Recent-Users tables → `Reveal`. Primitives honor + `prefers-reduced-motion` and resolve to **opacity 1** (no element stranded transparent → no + contrast/a11y regression; SSR-safe `prefersReducedMotion`). Applied to the auth-gated dashboard + only (not scanned by the public e2e set), per the tracker-web axe/opacity caution. test + **21 files / 170 tests** (+2, happy-dom asserts primitives end visible + render all children); + typecheck+lint+build green; format:check no new failures; e2e unchanged. - [ ] **UX-6 — System banners (conditional):** if a real broadcasts/maintenance feed is reachable, add `@bytelyst/notifications-ui` `BannerStack`/`Announcement` in `(dashboard)/layout.tsx`. `NotificationCenter` only with a real feed; else **defer**. @@ -181,7 +189,7 @@ pnpm --filter @bytelyst/admin-web test:e2e # Playwright + @axe-core (no new fa ``` Setup : UX-1 ✅ -Adopt : UX-2 ✅ UX-3 ✅ UX-4 ✅ UX-5 ⬜ UX-6 ⬜(cond) +Adopt : UX-2 ✅ UX-3 ✅ UX-4 ✅ UX-5 ✅ UX-6 ⬜(cond) Cross : CC.1 ⬜ CC.2 ⬜ CC.3 ⬜ CC.4 ⬜ CC.5 ⬜ CC.6 ⬜ ``` diff --git a/dashboards/admin-web/package.json b/dashboards/admin-web/package.json index ddf9218d..272ae1dc 100644 --- a/dashboards/admin-web/package.json +++ b/dashboards/admin-web/package.json @@ -38,6 +38,7 @@ "@bytelyst/errors": "workspace:*", "@bytelyst/extraction": "workspace:*", "@bytelyst/logger": "workspace:*", + "@bytelyst/motion": "workspace:*", "@bytelyst/react-auth": "workspace:*", "@bytelyst/telemetry-client": "workspace:*", "@bytelyst/ui": "workspace:*", diff --git a/dashboards/admin-web/src/__tests__/motion.test.tsx b/dashboards/admin-web/src/__tests__/motion.test.tsx new file mode 100644 index 00000000..b62c35d8 --- /dev/null +++ b/dashboards/admin-web/src/__tests__/motion.test.tsx @@ -0,0 +1,62 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { Reveal, StaggerList } from '@bytelyst/motion'; + +/** + * UX-5 — motion guard. The shared motion primitives must end fully visible + * (no element stranded at opacity:0, which would trip contrast / a11y) and + * must render all their content. We assert the reduced-motion / disabled path + * (what `prefers-reduced-motion` users and SSR-stable snapshots get). + */ +describe('motion primitives stay visible + render content', () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(() => { + (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = + true; + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + }); + + it('Reveal renders its child and resolves to visible (opacity 1)', () => { + act(() => { + root.render( + + Revealed content + + ); + }); + const el = container.querySelector('[data-testid="bl-reveal"]') as HTMLElement | null; + expect(el).toBeTruthy(); + expect(el!.getAttribute('data-visible')).toBe('true'); + expect(el!.style.opacity).toBe('1'); + expect(container.textContent).toContain('Revealed content'); + }); + + it('StaggerList renders every child, each visible', () => { + act(() => { + root.render( + +
alpha
+
beta
+
gamma
+
+ ); + }); + const reveals = container.querySelectorAll('[data-testid="bl-reveal"]'); + expect(reveals.length).toBe(3); + reveals.forEach(r => expect(r.getAttribute('data-visible')).toBe('true')); + expect(container.textContent).toContain('alpha'); + expect(container.textContent).toContain('beta'); + expect(container.textContent).toContain('gamma'); + }); +}); diff --git a/dashboards/admin-web/src/app/(dashboard)/page.tsx b/dashboards/admin-web/src/app/(dashboard)/page.tsx index da56a67a..9211b795 100644 --- a/dashboards/admin-web/src/app/(dashboard)/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/page.tsx @@ -35,6 +35,7 @@ import { type RevenueAnalytics, } from '@/lib/api'; import { PageHeader } from '@bytelyst/dashboard-components'; +import { Reveal, StaggerList } from '@bytelyst/motion'; import { AreaChart, BarChart } from '@/components/charts'; import { seriesValues, dateBars } from '@/lib/chart-data'; @@ -310,7 +311,12 @@ export default function DashboardPage() { ))} ) : ( -
+ {kpiCards.map(card => ( @@ -344,7 +350,7 @@ export default function DashboardPage() { ))} -
+ )} {/* Charts Row */} @@ -390,7 +396,7 @@ export default function DashboardPage() { )} {/* Bottom Row: Model Usage + Recent Users */} -
+ {/* Model Usage */} @@ -476,7 +482,7 @@ export default function DashboardPage() { )} -
+ ); } diff --git a/dashboards/admin-web/vitest.config.ts b/dashboards/admin-web/vitest.config.ts index 734eb6b6..2c1e73f0 100644 --- a/dashboards/admin-web/vitest.config.ts +++ b/dashboards/admin-web/vitest.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ // The real Next/webpack build already dedupes these to admin-web's React. server: { deps: { - inline: [/@bytelyst\/(charts|data-viz|command-palette|dashboard-components)/], + inline: [/@bytelyst\/(charts|data-viz|command-palette|dashboard-components|motion)/], }, }, coverage: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15b58758..7564f029 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: '@bytelyst/logger': specifier: workspace:* version: link:../../packages/logger + '@bytelyst/motion': + specifier: workspace:* + version: link:../../packages/motion '@bytelyst/react-auth': specifier: workspace:* version: link:../../packages/react-auth