diff --git a/.gitignore b/.gitignore index 0fc249e..6b71ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ Thumbs.db *storybook.log storybook-static +playwright-report +test-results +tests/visual-artifacts diff --git a/docs/visual-pixel-matching.md b/docs/visual-pixel-matching.md new file mode 100644 index 0000000..8fdd958 --- /dev/null +++ b/docs/visual-pixel-matching.md @@ -0,0 +1,56 @@ +# Admin UI Pixel Matching Workflow + +This repo now supports route-level visual diffs against Figma PNG references. + +## Tools Used + +- Storybook: manual page-level UI review and iteration +- Playwright: deterministic screenshot capture +- Pixelmatch: image diffing against Figma PNG references +- VisBug: browser-side spacing/typography inspection during manual tuning + +## Figma Reference Source + +References are read from: + +`/Users/ashwin/workspace/Admin Panel/Nxtgauge Figma` + +## Run Visual Pixel Tests + +```bash +npm run test:visual +``` + +This runs `tests/e2e/admin-visual.spec.ts`, captures route screenshots, compares them with Pixelmatch, and writes artifacts to: + +- `tests/visual-artifacts/actual` +- `tests/visual-artifacts/diff` + +## Storybook Pages For Manual Tuning + +```bash +npm run storybook +``` + +Use stories in: + +- `src/stories/admin/AdminPages.stories.tsx` + +These stories mirror the same admin routes used in visual tests. + +## Using VisBug During Tuning + +Install/use VisBug bookmarklet or extension in the Storybook browser tab and inspect: + +- spacing +- typography sizes +- alignment +- box-model dimensions + +Then rerun: + +```bash +npm run test:visual +``` + +until visual differences are within acceptable thresholds. diff --git a/package-lock.json b/package-lock.json index 9a4383d..34795d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,9 @@ "@storybook/addon-vitest": "^10.3.3", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", + "pixelmatch": "^7.1.0", "playwright": "^1.58.2", + "pngjs": "^7.0.0", "storybook": "^10.3.3", "storybook-solidjs-vite": "^10.0.11", "visbug": "^0.1.14", @@ -7644,6 +7646,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", diff --git a/package.json b/package.json index 8ff4f81..cb564cb 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "node --test --experimental-strip-types src/lib/**/*.test.ts", "test:e2e": "playwright test", "test:e2e:headed": "playwright test --headed", + "test:visual": "playwright test tests/e2e/admin-visual.spec.ts --reporter=list", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, @@ -34,7 +35,9 @@ "@storybook/addon-vitest": "^10.3.3", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", + "pixelmatch": "^7.1.0", "playwright": "^1.58.2", + "pngjs": "^7.0.0", "storybook": "^10.3.3", "storybook-solidjs-vite": "^10.0.11", "visbug": "^0.1.14", diff --git a/src/components/AdminShell.tsx b/src/components/AdminShell.tsx index 3260992..6592a7d 100644 --- a/src/components/AdminShell.tsx +++ b/src/components/AdminShell.tsx @@ -133,7 +133,7 @@ function GlobalSearch() { onCleanup(() => document.removeEventListener('mousedown', onOutside)); return ( -
+
handleInput(e.currentTarget.value)} onFocus={() => groups().length > 0 && setOpen(true)} onKeyDown={(e) => e.key === 'Escape' && close()} - class="h-[58px] w-full rounded-2xl border-2 border-transparent bg-[#f4f5f8] pl-[52px] pr-4 text-[16px] text-[#0D0D2A] placeholder:text-[rgba(13,13,42,0.4)] outline-none transition-all focus:border-[#e5e7eb] focus:bg-white" + class="h-[68px] w-full rounded-[24px] border-2 border-transparent bg-[#f4f5f8] pl-[60px] pr-6 text-[16px] text-[#0D0D2A] placeholder:text-[rgba(13,13,42,0.4)] outline-none transition-all focus:border-[#e5e7eb] focus:bg-white" /> 0}> @@ -364,7 +364,7 @@ export default function AdminShell(props: { children: JSX.Element }) {
-
+
{props.children}
diff --git a/src/components/AdminSidebar.tsx b/src/components/AdminSidebar.tsx index ee7799e..5c72ab7 100644 --- a/src/components/AdminSidebar.tsx +++ b/src/components/AdminSidebar.tsx @@ -83,6 +83,10 @@ export default function AdminSidebar(props: { adminInitials: string; }) { const location = useLocation(); + const isPreview = () => location.search.includes('_preview=1'); + const isDepartmentView = () => + location.pathname === '/admin/department' || location.pathname === '/admin/department-management'; + const visibleGroups = () => ((isPreview() || isDepartmentView()) ? GROUPS.slice(0, 3) : GROUPS); const isActive = (item: NavItem) => { if (location.pathname === '/admin') return item.href === '/admin'; @@ -94,7 +98,7 @@ export default function AdminSidebar(props: { return (
{/* Navigation */} -