feat(admin-web): add @bytelyst/motion reveal/stagger on dashboard (UX-5)
- @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>
This commit is contained in:
parent
89a56739bd
commit
aa0e67d219
@ -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** `<SHA-UX5>` · `@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 ⬜
|
||||
```
|
||||
|
||||
|
||||
@ -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:*",
|
||||
|
||||
62
dashboards/admin-web/src/__tests__/motion.test.tsx
Normal file
62
dashboards/admin-web/src/__tests__/motion.test.tsx
Normal file
@ -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(
|
||||
<Reveal disableMotion from="up">
|
||||
<span>Revealed content</span>
|
||||
</Reveal>
|
||||
);
|
||||
});
|
||||
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(
|
||||
<StaggerList disableMotion from="up">
|
||||
<div>alpha</div>
|
||||
<div>beta</div>
|
||||
<div>gamma</div>
|
||||
</StaggerList>
|
||||
);
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
@ -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() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<StaggerList
|
||||
as="div"
|
||||
from="up"
|
||||
stagger={50}
|
||||
className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"
|
||||
>
|
||||
{kpiCards.map(card => (
|
||||
<Card key={card.title} className="transition-shadow hover:shadow-md">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
@ -344,7 +350,7 @@ export default function DashboardPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</StaggerList>
|
||||
)}
|
||||
|
||||
{/* Charts Row */}
|
||||
@ -390,7 +396,7 @@ export default function DashboardPage() {
|
||||
)}
|
||||
|
||||
{/* Bottom Row: Model Usage + Recent Users */}
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<Reveal from="up" className="grid gap-6 lg:grid-cols-2">
|
||||
{/* Model Usage */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -476,7 +482,7 @@ export default function DashboardPage() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user