Skip to content

Commit ef76118

Browse files
Custom menu points and info-pages routing (#1256)
* Add site structure wip * wip categories in menu * remove comment * biome settings * fixes * remove slug * Change to nullish coalescing * Filter out documents that are singltons * make it loopy
1 parent 50c02b3 commit ef76118

File tree

7 files changed

+158
-9
lines changed

7 files changed

+158
-9
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
44
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
55
],
6-
"editor.defaultFormatter": "biomejs.biome"
6+
"editor.defaultFormatter": "biomejs.biome",
7+
"[typescriptreact]": {
8+
"editor.defaultFormatter": "biomejs.biome"
9+
}
710
}

apps/docs/app/routes/_docs.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,19 @@ import { Link } from '@tanstack/react-router';
2121
import { defineQuery } from 'groq';
2222
import { useEffect, useState } from 'react';
2323

24-
const COMPONENTS_NAVIGATION_QUERY = defineQuery(
25-
// make sure the slug is always a string so we don't have add fallback value in code just to make TypeScript happy
26-
`*[_type == "component"]{ _id, name, 'slug': coalesce(slug.current, ''), componentState} | order(name asc)`,
27-
);
24+
const NAVIGATION_QUERY = defineQuery(`{
25+
"components": *[_type == "component"]{ _id, name, 'slug': coalesce(slug.current, ''), componentState} | order(name asc),
26+
"menu": *[_type == "menu"][0]{
27+
categories[]->{
28+
title,
29+
"slug": slug.current,
30+
categoryItems[]->{
31+
name,
32+
"slug": slug.current
33+
}
34+
}
35+
}
36+
}`);
2837

2938
// This is the shared layout for all the Grunnmuren docs pages that are "public", ie not the Sanity studio
3039
export const Route = createFileRoute('/_docs')({
@@ -37,7 +46,7 @@ export const Route = createFileRoute('/_docs')({
3746
},
3847
],
3948
}),
40-
loader: () => sanityFetch({ query: COMPONENTS_NAVIGATION_QUERY }),
49+
loader: () => sanityFetch({ query: NAVIGATION_QUERY }),
4150
});
4251

4352
function RootLayout() {

apps/docs/app/ui/main-nav.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,27 @@ export const MainNav = ({ className }: MainNavProps) => {
7878
const routeApi = getRouteApi('/_docs');
7979
const { data } = routeApi.useLoaderData();
8080

81-
const componentsNavLinks = data.map((component) => ({
81+
// Extract components and menu data
82+
const components = data.components ?? [];
83+
const menuData = data.menu ?? { categories: [] };
84+
85+
const componentsNavLinks = components.map((component) => ({
8286
to: `/komponenter/${component.slug}`,
8387
title: component.name as string,
8488
componentState: component.componentState,
8589
}));
8690

91+
// Transform categories into nav items
92+
const categoryNavItems =
93+
menuData.categories?.map((category) => ({
94+
title: category.title,
95+
subNavItems:
96+
category.categoryItems?.map((item) => ({
97+
to: `/${item.slug}`,
98+
title: item.name,
99+
})) ?? [],
100+
})) ?? [];
101+
87102
return (
88103
<nav
89104
className={cx(
@@ -93,10 +108,16 @@ export const MainNav = ({ className }: MainNavProps) => {
93108
aria-label="Navigasjonsmeny for grunnmuren"
94109
>
95110
<ul>
96-
<MainNavItem title="Komponenter" subNavItems={componentsNavLinks} />
111+
{categoryNavItems.map((categoryItem) => (
112+
<MainNavItem key={categoryItem.title} {...categoryItem} />
113+
))}
114+
115+
<hr />
97116
{mainNavItems.map((mainNavItem) => (
98117
<MainNavItem key={mainNavItem.title} {...mainNavItem} />
99118
))}
119+
120+
<MainNavItem title="Komponenter" subNavItems={componentsNavLinks} />
100121
</ul>
101122
</nav>
102123
);

apps/docs/sanity.config.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,82 @@ export default defineConfig({
1414
basePath: '/studio',
1515
title: 'Grunnmuren - Sanity Studio',
1616
auth: obosAuthStore({ dataset }),
17-
plugins: [structureTool(), visionTool(), codeInput(), table()],
17+
plugins: [
18+
structureTool({
19+
structure: async (S, context) => {
20+
const CATEGORIES = await context
21+
.getClient({ apiVersion: '2025-03-21' })
22+
.fetch(`(*[_type == "category"])`);
23+
24+
return S.list()
25+
.title('Content')
26+
.items([
27+
S.listItem()
28+
.id('menuAndCategories')
29+
.title('Menu and categories')
30+
.child(
31+
S.list()
32+
.title('Menu and categories')
33+
.items([
34+
// Our singleton type has a list item with a custom child
35+
S.listItem()
36+
.title('Menu')
37+
.id('menu')
38+
.child(
39+
// Instead of rendering a list of documents, we render a single
40+
// document, specifying the `documentId` manually to ensure
41+
// that we're editing the single instance of the document
42+
S.document()
43+
.title('Menu')
44+
.schemaType('menu')
45+
.documentId('menu'),
46+
),
47+
S.divider(),
48+
...CATEGORIES.map((category) => {
49+
return S.listItem()
50+
.title(category.title)
51+
.id(category._id)
52+
.child(
53+
// Instead of rendering a list of documents, we render a single
54+
// document, specifying the `documentId` manually to ensure
55+
// that we're editing the single instance of the document
56+
S.document()
57+
.title(category.title)
58+
.schemaType(category._type)
59+
.documentId(category._id),
60+
);
61+
}),
62+
]),
63+
),
64+
S.divider(),
65+
S.documentTypeListItem('component').title('Components'),
66+
S.documentTypeListItem('info').title('Info'),
67+
]);
68+
},
69+
}),
70+
visionTool(),
71+
codeInput(),
72+
table(),
73+
],
1874
schema: {
1975
types: schemaTypes,
2076
},
77+
document: {
78+
newDocumentOptions: (templateItems, { creationContext }) => {
79+
// Define the singleton document types
80+
const singletonTypes = new Set(['menu']);
81+
// Check if the context is that of the top level "Create" button in the header
82+
if (creationContext.type === 'global') {
83+
const nonSingletonTemplateItems = [] as typeof templateItems;
84+
for (const item of templateItems) {
85+
if (!singletonTypes.has(item.templateId)) {
86+
nonSingletonTemplateItems.push(item);
87+
}
88+
}
89+
return nonSingletonTemplateItems;
90+
}
91+
92+
return templateItems;
93+
},
94+
},
2195
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineField, defineType } from 'sanity';
2+
3+
export const category = defineType({
4+
name: 'category',
5+
title: 'Category',
6+
type: 'document',
7+
fields: [
8+
defineField({
9+
name: 'title',
10+
title: 'Title',
11+
type: 'string',
12+
validation: (Rule) => Rule.required(),
13+
}),
14+
defineField({
15+
name: 'categoryItems',
16+
title: 'Category items',
17+
type: 'array',
18+
of: [{ type: 'reference', to: [{ type: 'info' }] }],
19+
}),
20+
],
21+
});

apps/docs/studio/documents/menu.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineField, defineType } from 'sanity';
2+
3+
export const menu = defineType({
4+
name: 'menu',
5+
title: 'Menu',
6+
type: 'document',
7+
fields: [
8+
defineField({
9+
name: 'categories',
10+
title: 'Categories',
11+
description:
12+
'The items in the left menu. The order in sanity will be reflected in the app',
13+
type: 'array',
14+
of: [{ type: 'reference', to: [{ type: 'category' }] }],
15+
}),
16+
],
17+
});

apps/docs/studio/schema-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { category } from './documents/category';
12
import component from './documents/component';
23
import info from './documents/info';
4+
import { menu } from './documents/menu';
35
import content from './objects/content';
46
import imageWithCaption from './objects/image-with-caption';
57
import liveCodeBlock from './objects/live-code-block';
@@ -9,6 +11,8 @@ import staticCodeBlock from './objects/static-code-block';
911
export const schemaTypes = [
1012
component,
1113
info,
14+
menu,
15+
category,
1216
content,
1317
staticCodeBlock,
1418
liveCodeBlock,

0 commit comments

Comments
 (0)