Skip to content

Commit 9e40b72

Browse files
committed
Configure nav-menu variant and class via site config.
1 parent f1f0445 commit 9e40b72

9 files changed

Lines changed: 160 additions & 3 deletions

File tree

‎frontend/purestack.studio/components/site/nav-menu/nav-menu.mdx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,22 @@ createApp(
376376
</TabPane>
377377
</Tabs>
378378

379+
## From site config
380+
381+
The menu the doc layout docks in its sidebar takes its tone, variant and extra classes from the `navigation` section of `siteConfig.json`. A page can still override the tone with `nav.tone` in its frontmatter.
382+
383+
```json
384+
{
385+
"navigation": {
386+
"tone": "accent",
387+
"variant": "glass",
388+
"class": "spotlight-from-top-right"
389+
}
390+
}
391+
```
392+
393+
Docked in the sidebar, the menu stays square and borderless whatever the variant, so the variant sets its fill and colors.
394+
379395
## Your own items
380396

381397
Pass `items` to show a tree that is not the site navigation, and `currentUrl` to say which page is current. Without `currentUrl`, NavMenu uses the page being rendered, then the browser location. Groups are items with `children`; a group can have its own `url` too.

‎packages/ts-common/src/navMenu-types.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface NavigationConfig {
1111
sortBy?: NavigationSort
1212
roots?: string[]
1313
tone?: SemanticTone
14+
/** Treatment of the navigation panel, such as flat, surfaceAlt or glass. */
15+
variant?: string
16+
/** Extra classes for the navigation panel. */
17+
class?: string
1418
}
1519

1620
export interface NavItem {

‎packages/ts-components/src/standard/navMenu/navMenu.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ describe('NavMenu rendering', () => {
7676
expect(customHtml).not.toContain('tone-fill-flat')
7777
})
7878

79+
it('adds a given class to the navigation panel', () => {
80+
const cleanup = ensureDomGlobals()
81+
const html = renderApp(
82+
`<NavMenu :items="[]" class="spotlight-from-top-right"></NavMenu>`,
83+
{
84+
components: {
85+
...defineButtonComponents(),
86+
...defineFlexComponents(),
87+
...defineIconComponents(getSvgIcon),
88+
...definePanelComponents(),
89+
...defineSignInComponents(),
90+
...defineNavigationComponents(),
91+
},
92+
context: createTestContext(),
93+
},
94+
)
95+
cleanup()
96+
97+
const navClass = html.match(/<nav class="([^"]*)"/)?.[1].split(' ')
98+
expect(navClass).toContain('spotlight-from-top-right')
99+
expect(navClass).toContain('tone-fill-flat')
100+
})
101+
79102
it('marks the currentUrl item as the current page and opens its group', () => {
80103
const cleanup = ensureDomGlobals()
81104
const components = {

‎packages/ts-ssg/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ Config comes from:
151151
"navFileName": "_nav.json",
152152
"maxDepth": 3,
153153
"includeIndex": true,
154-
"sortBy": "order"
154+
"sortBy": "order",
155+
"tone": "neutral",
156+
"variant": "flat",
157+
"class": ""
155158
},
156159
"sitemap": {
157160
"enabled": true,

‎packages/ts-ssg/schema/siteConfig.schema.json‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,35 @@
383383
],
384384
"description": "Navigation menu semantic tone.",
385385
"default": "neutral"
386+
},
387+
"variant": {
388+
"type": "string",
389+
"enum": [
390+
"solid",
391+
"surface",
392+
"surfaceAlt",
393+
"spotlight",
394+
"glass",
395+
"flat",
396+
"flatAlt",
397+
"flatSolid",
398+
"outlineFill",
399+
"outline",
400+
"subtle",
401+
"subtleBtn",
402+
"link",
403+
"sheen",
404+
"underline",
405+
"rail",
406+
"bracket",
407+
"none"
408+
],
409+
"description": "Navigation menu treatment. Docked in the doc sidebar, the menu stays square and borderless whatever the variant.",
410+
"default": "flat"
411+
},
412+
"class": {
413+
"type": "string",
414+
"description": "Extra classes for the navigation menu, such as spotlight-from-top-right."
386415
}
387416
}
388417
},

‎packages/ts-ssg/src/build/renderer.test.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,59 @@ describe('renderPage consent integration', () => {
317317
expect(html).not.toContain('page-toc')
318318
})
319319

320+
it('passes the configured navigation variant and class to the nav menu', async () => {
321+
const site = resolveSiteConfig({
322+
rootDir: process.cwd(),
323+
navigation: {
324+
tone: 'accent',
325+
variant: ' glass ',
326+
class: 'spotlight-from-top-right',
327+
},
328+
})
329+
const html = await renderPage({
330+
bodyHtml: '<p>Hello</p>',
331+
pageInfo: {
332+
relPath: 'index.md',
333+
urlPath: '/',
334+
frontmatter: normalizeFrontmatter({}),
335+
},
336+
navigation: {
337+
mode: 'auto',
338+
folder: '',
339+
root: '',
340+
items: [{ title: 'Home', url: '/' }],
341+
global: [],
342+
tone: 'accent',
343+
},
344+
site,
345+
})
346+
expect(html).toMatch(
347+
/<nav-menu tone="accent" variant="glass" class="spotlight-from-top-right">/,
348+
)
349+
})
350+
351+
it('leaves the nav menu on its own variant when none is configured', async () => {
352+
const site = resolveSiteConfig({ rootDir: process.cwd() })
353+
const html = await renderPage({
354+
bodyHtml: '<p>Hello</p>',
355+
pageInfo: {
356+
relPath: 'index.md',
357+
urlPath: '/',
358+
frontmatter: normalizeFrontmatter({}),
359+
},
360+
navigation: {
361+
mode: 'auto',
362+
folder: '',
363+
root: '',
364+
items: [{ title: 'Home', url: '/' }],
365+
global: [],
366+
tone: 'neutral',
367+
},
368+
site,
369+
})
370+
expect(html).toContain('<nav-menu tone="neutral">')
371+
})
372+
320373
it('does not render navigation shell when showNav is false', async () => {
321374
const site = resolveSiteConfig({ rootDir: process.cwd() })
322375
const html = await renderPage({

‎packages/ts-ssg/src/navigation/config.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,24 @@ export function resolveNavigationConfig(
4040
const sortBy = resolveSort(merged.sortBy)
4141
const roots = resolveNavigationRoots(merged.roots)
4242
const tone = pickSemanticTone(merged.tone) ?? DEFAULT_NAV_CONFIG.tone
43-
return { mode, navFileName, maxDepth, includeIndex, sortBy, roots, tone }
43+
const variant = resolveText(merged.variant)
44+
const className = resolveText(merged.class)
45+
return {
46+
mode,
47+
navFileName,
48+
maxDepth,
49+
includeIndex,
50+
sortBy,
51+
roots,
52+
tone,
53+
...(variant ? { variant } : {}),
54+
...(className ? { class: className } : {}),
55+
}
56+
}
57+
58+
function resolveText(value: unknown) {
59+
if (typeof value !== 'string') return undefined
60+
return value.trim() || undefined
4461
}
4562

4663
function resolveMode(mode: NavigationMode | undefined): NavigationMode {

‎packages/ts-ssg/src/navigation/model.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface ResolvedNavigationConfig {
1717
sortBy: NavigationSort
1818
roots: string[]
1919
tone: SemanticTone
20+
variant?: string
21+
class?: string
2022
}
2123

2224
export interface NavigationTree {

‎packages/ts-ssg/src/templates/page-templates.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,17 @@ function renderDocTemplate({
6767
? [
6868
h('aside')
6969
.attr({ class: 'doc-sidebar', id: 'doc-sidebar' })
70-
.push(h('nav-menu').attr({ tone: navigation?.tone })),
70+
.push(
71+
h('nav-menu').attr({
72+
tone: navigation?.tone,
73+
...(site.navigation.variant
74+
? { variant: site.navigation.variant }
75+
: {}),
76+
...(site.navigation.class
77+
? { class: site.navigation.class }
78+
: {}),
79+
}),
80+
),
7181
]
7282
: []),
7383
h('main')

0 commit comments

Comments
 (0)