From d980f4cf2f0930fceac109424c419950ef72749c Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Mon, 12 Oct 2026 18:19:53 +0200 Subject: [PATCH] feat: MenuNav layer for the SummerCMS menu API - typed client and useMenu/useMenuApi (SSR, menu:: key, server and public bases, x-forwarded-for forwarded also on cached renders) - MenuNav and recursive MenuNavItem: WAI disclosure navigation, URL allowlist with a text fallback, rel on _blank, active link and trail, mobile toggle, Esc / focus-out / outside pointer / route change closing - menu tokens and base CSS, README - node tests (link rules, client, server-rendered NavItem) and a playground SSR smoke against a fixture API --- .gitignore | 8 + .nvmrc | 1 + .playground/app/app.vue | 24 + .playground/nuxt.config.ts | 6 + README.md | 128 + app/assets/css/menu-base.css | 247 + app/assets/css/menu-tokens.css | 33 + app/components/Menu/Nav.vue | 125 + app/components/Menu/NavItem.vue | 189 + app/composables/useMenu.ts | 20 + app/composables/useMenuApi.ts | 28 + app/utils/client.ts | 43 + app/utils/menuLinks.ts | 103 + app/utils/types.ts | 28 + nuxt.config.ts | 33 + package.json | 25 + pnpm-lock.yaml | 6854 +++++++++++++++++++++++++ pnpm-workspace.yaml | 4 + server/plugins/menu-client-address.ts | 17 + testing/expect.txt | 24 + testing/fixture-api.mjs | 67 + testing/fixtures/menus/main.json | 39 + testing/smoke-ssr.mjs | 192 + tests/client.test.ts | 78 + tests/menuLinks.test.ts | 137 + tests/render.ts | 60 + tsconfig.json | 16 + 27 files changed, 8529 insertions(+) create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 .playground/app/app.vue create mode 100644 .playground/nuxt.config.ts create mode 100644 README.md create mode 100644 app/assets/css/menu-base.css create mode 100644 app/assets/css/menu-tokens.css create mode 100644 app/components/Menu/Nav.vue create mode 100644 app/components/Menu/NavItem.vue create mode 100644 app/composables/useMenu.ts create mode 100644 app/composables/useMenuApi.ts create mode 100644 app/utils/client.ts create mode 100644 app/utils/menuLinks.ts create mode 100644 app/utils/types.ts create mode 100644 nuxt.config.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 server/plugins/menu-client-address.ts create mode 100644 testing/expect.txt create mode 100644 testing/fixture-api.mjs create mode 100644 testing/fixtures/menus/main.json create mode 100644 testing/smoke-ssr.mjs create mode 100644 tests/client.test.ts create mode 100644 tests/menuLinks.test.ts create mode 100644 tests/render.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b042b30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.nuxt/ +.output/ +.data/ +.playground/.nuxt/ +.playground/.output/ +.env +app/components/Menu/.navitem-*.compiled.ts diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..85e5027 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.22.0 diff --git a/.playground/app/app.vue b/.playground/app/app.vue new file mode 100644 index 0000000..3db37b1 --- /dev/null +++ b/.playground/app/app.vue @@ -0,0 +1,24 @@ + + + diff --git a/.playground/nuxt.config.ts b/.playground/nuxt.config.ts new file mode 100644 index 0000000..cb4d490 --- /dev/null +++ b/.playground/nuxt.config.ts @@ -0,0 +1,6 @@ +// Local playground: an app that only extends the layer and renders one menu +// (app.vue). testing/smoke-ssr.mjs builds it against the fixture API. +export default defineNuxtConfig({ + extends: ['..'], + compatibilityDate: '2025-07-15', +}) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e58c06b --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# vue-menu-layer + +A Nuxt 4 layer that renders a menu of the SummerCMS menu plugin as an accessible site navigation. + +```bash +pnpm add git+https://git.golem15.com/golem15/vue-menu-layer.git#v0.1.0 +``` + +## Overview + +The layer is consumed with `extends: ['vue-menu-layer']`. It reads one route of the menu plugin's anonymous API, `GET /_menu/api/v1/menus/{code}`, during SSR, so the first paint already has the menu, and renders it with ``. Editors manage menus in the SummerCMS admin; the site needs no menu data of its own. + +The layer is neutral: it ships no site colours, no site copy and no Nuxt modules. Its look comes from `--menu-*` CSS custom properties, which an app overrides, and every component can be replaced by a file of the same name in the app. + +## Features + +- **`MenuNav`**: a `