diff --git a/apps/web/package.json b/apps/web/package.json index 8f5f460a..81446c82 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,9 +17,12 @@ "dependencies": { "@gonext/blocks-core": "workspace:*", "@gonext/blocks-sdk": "workspace:*", + "clsx": "^2.1.1", + "lucide-react": "^0.469.0", "next": "^15.0.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tailwind-merge": "^2.5.5" }, "devDependencies": { "@gonext/test-config": "workspace:*", @@ -29,9 +32,12 @@ "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitest/coverage-v8": "^1.6.0", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-config-next": "^15.0.0", "jsdom": "^24.0.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", "typescript": "^5.6.0", "vitest": "^1.6.0" } diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 00000000..1314e4a5 --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,16 @@ +/** + * PostCSS config — required for Next.js to pick up Tailwind v3. + * + * Next.js auto-detects `postcss.config.{js,mjs,cjs}` at the app root + * and runs the listed plugins over every imported `.css` file (we + * import `./globals.css` from the root layout). Without this file + * Next would fall through to its default postcss preset and silently + * skip the Tailwind processing pass, so the `@tailwind base/components/utilities` + * directives at the top of globals.css would never expand. + */ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/src/app/PublicShell.test.tsx b/apps/web/src/app/PublicShell.test.tsx index e77d12a8..47f6347a 100644 --- a/apps/web/src/app/PublicShell.test.tsx +++ b/apps/web/src/app/PublicShell.test.tsx @@ -16,6 +16,7 @@ describe('PublicShell', () => { bodyHtml="
H
M
" cssCustomProperties=":root{--x:1}" templateBasename="single.html" + withChrome={false} />, ); expect(container.querySelector('header')).not.toBeNull(); @@ -29,6 +30,7 @@ describe('PublicShell', () => { bodyHtml="" cssCustomProperties=":root{--y:2}" templateBasename="index.html" + withChrome={false} />, ); const style = container.querySelector('style[data-gn-theme]'); @@ -41,9 +43,47 @@ describe('PublicShell', () => { bodyHtml="

hi

" cssCustomProperties="" templateBasename="archive-book.tsx" + withChrome={false} />, ); const site = container.querySelector('.gn-site'); expect(site?.getAttribute('data-gn-template')).toBe('archive-book.tsx'); }); + + it('renders the brand chrome (nav + footer) by default', () => { + const { container } = render( + , + ); + // The marketing nav uses a sticky pill on the forest surface; + // checking for the aria-label keeps the assertion forward- + // compatible with class-name changes. + expect( + container.querySelector('nav[aria-label="Primary"]'), + ).not.toBeNull(); + // The footer is a real