Skip to content

Commit 82bea64

Browse files
alexanbjaulonm
authored andcommitted
docs: presentation mode
1 parent cb683e4 commit 82bea64

File tree

15 files changed

+709
-3
lines changed

15 files changed

+709
-3
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dist/
55
storybook-static/
66
next-env.d.ts
77

8+
packages/icons-react/icons.tsx
89

910
# editors
1011
.idea/
@@ -17,5 +18,4 @@ next-env.d.ts
1718

1819
# Secrets
1920
.FIGMA_TOKEN
20-
.vercel
21-
packages/icons-react/icons.tsx
21+
.env.local

apps/docs/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Creata a viewer token at sanity.io/manage
2+
SANITY_VIEWER_TOKEN=

apps/docs/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
public/resources/icons/
55
public/storybook
66
component-props.ts
7+
docgen.ts
8+
9+
.env

apps/docs/app/routes/_docs.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { sanityFetch } from '@/lib/sanity';
2+
import { VisualEditing } from '@/lib/visual-editing';
3+
import appCss from '@/styles/app.css?url';
4+
import { Footer } from '@/ui/footer';
5+
import { MainNav } from '@/ui/main-nav';
6+
import { GrunnmurenProvider } from '@obosbbl/grunnmuren-react';
7+
import {
8+
type NavigateOptions,
9+
Outlet,
10+
ScrollRestoration,
11+
type ToOptions,
12+
createFileRoute,
13+
useRouter,
14+
} from '@tanstack/react-router';
15+
import { defineQuery } from 'groq';
16+
17+
const COMPONENTS_NAVIGATION_QUERY = defineQuery(
18+
// make sure the slug is always a string so we don't have add fallback value in code just to make TypeScript happy
19+
`*[_type == "component"]{ _id, name, 'slug': coalesce(slug.current, '')} | order(name asc)`,
20+
);
21+
22+
// This is the shared layout for all the Grunnmuren docs pages that are "public", ie not the Sanity studio
23+
export const Route = createFileRoute('/_docs')({
24+
component: RootLayout,
25+
head: () => ({
26+
links: [{ rel: 'stylesheet', href: appCss }],
27+
meta: [
28+
{
29+
title: "Grunnmuren - OBOS' Design System",
30+
},
31+
],
32+
}),
33+
loader: () => sanityFetch({ query: COMPONENTS_NAVIGATION_QUERY }),
34+
});
35+
36+
function RootLayout() {
37+
const router = useRouter();
38+
39+
return (
40+
<>
41+
<GrunnmurenProvider
42+
locale="nb"
43+
// This integrates RAC/Grunnmuren with TanStack router
44+
// Giving us typesafe routes
45+
// See https://react-spectrum.adobe.com/react-aria/routing.html#tanstack-router
46+
navigate={(to, options) => router.navigate({ to, ...options })}
47+
useHref={(to) => router.buildLocation({ to }).href}
48+
>
49+
<div className="grid min-h-screen lg:flex">
50+
<div className="flex grow flex-col px-6">
51+
<main className="grow">
52+
<Outlet />
53+
</main>
54+
<Footer />
55+
</div>
56+
<MainNav />
57+
</div>
58+
</GrunnmurenProvider>
59+
<ScrollRestoration />
60+
<VisualEditing />
61+
</>
62+
);
63+
}
64+
65+
// See comments on GrunnmurenProvider in <RootLayout />
66+
declare module 'react-aria-components' {
67+
interface RouterConfig {
68+
href: ToOptions['to'];
69+
routerOptions: Omit<NavigateOptions, keyof ToOptions>;
70+
}
71+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as badgeExamples from '@/examples/badge';
2+
import * as buttonExamples from '@/examples/button';
3+
import { sanityFetch } from '@/lib/sanity.server';
4+
import { Content } from '@/ui/content';
5+
import { PropsTable } from '@/ui/props-table';
6+
import { createFileRoute, notFound } from '@tanstack/react-router';
7+
import * as props from 'docgen';
8+
import { defineQuery } from 'groq';
9+
10+
const COMPONENT_QUERY = defineQuery(
11+
`*[_type == "component" && slug.current == $slug][0]{ content, "name": coalesce(name, '') }`,
12+
);
13+
14+
export const Route = createFileRoute('/_docs/komponenter/$slug')({
15+
component: Page,
16+
loader: async ({ params }) => {
17+
const res = await sanityFetch({
18+
data: {
19+
query: COMPONENT_QUERY,
20+
params: { slug: params.slug },
21+
},
22+
});
23+
24+
if (res.data == null) {
25+
throw notFound();
26+
}
27+
28+
const componentName = res.data.name;
29+
const componentProps = props[componentName].props;
30+
31+
return { data: res.data, componentProps };
32+
},
33+
});
34+
35+
function Page() {
36+
const { data, componentProps } = Route.useLoaderData();
37+
38+
// @ts-expect-error this works for now until we figure how to make the examples work better with Sanity
39+
const { scope, examples } = {
40+
Button: buttonExamples,
41+
Badge: badgeExamples,
42+
}[data.name];
43+
44+
return (
45+
<>
46+
<h1 className="heading-l mb-12 mt-9">{data.name}</h1>
47+
48+
<Content content={data.content ?? []} />
49+
50+
<PropsTable props={componentProps} />
51+
</>
52+
);
53+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createAPIFileRoute } from '@tanstack/start/api';
2+
import { deleteCookie, sendRedirect } from 'vinxi/http';
3+
4+
export const APIRoute = createAPIFileRoute('/api/preview-mode/disable')({
5+
GET: () => {
6+
deleteCookie('__sanity_preview', {
7+
path: '/',
8+
secure: import.meta.env.PROD,
9+
httpOnly: true,
10+
sameSite: 'strict',
11+
});
12+
sendRedirect('/');
13+
},
14+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { randomBytes } from 'node:crypto';
2+
import { client } from '@/lib/sanity';
3+
import { validatePreviewUrl } from '@sanity/preview-url-secret';
4+
import { createAPIFileRoute } from '@tanstack/start/api';
5+
import { SanityClient } from 'sanity';
6+
import { sendRedirect, setCookie } from 'vinxi/http';
7+
8+
export const APIRoute = createAPIFileRoute('/api/preview-mode/enable')({
9+
GET: async ({ request }) => {
10+
if (!process.env.SANITY_VIEWER_TOKEN) {
11+
throw new Response('Preview mode missing token', { status: 401 });
12+
}
13+
14+
const clientWithToken = client.withConfig({
15+
token: process.env.SANITY_VIEWER_TOKEN,
16+
});
17+
18+
const { isValid, redirectTo = '/' } = await validatePreviewUrl(
19+
clientWithToken,
20+
request.url,
21+
);
22+
23+
if (!isValid) {
24+
throw new Response('Invalid secret', { status: 401 });
25+
}
26+
27+
// we can use sameSite: 'strict' because we're running an embedded studio
28+
// setCookie('__sanity_preview', randomBytes(16).toString('hex'), {
29+
setCookie('__sanity_preview', 'true', {
30+
path: '/',
31+
secure: import.meta.env.PROD,
32+
httpOnly: true,
33+
sameSite: 'strict',
34+
});
35+
sendRedirect(redirectTo);
36+
},
37+
});

apps/docs/dktp/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ properties:
1616
containers:
1717
- image: dktprodacr.azurecr.io/grunnmuren/docs:${IMAGE_TAG}
1818
name: docs
19+
env:
20+
- name: SANITY_VIEWER_TOKEN
21+
secretRef: ${todo}
1922
resources:
2023
cpu: 0.25
2124
memory: 0.5Gi

apps/docs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929
"@sanity/client": "7.12.1",
3030
"@sanity/code-input": "6.0.3",
3131
"@sanity/image-url": "^1.2.0",
32+
"@sanity/presentation": "1.21.1",
33+
"@sanity/preview-url-secret": "2.1.0",
3234
"@sanity/table": "2.0.0",
3335
"@sanity/vision": "4.14.2",
36+
"@sanity/visual-editing": "2.12.0",
3437
"@tanstack/nitro-v2-vite-plugin": "1.133.19",
3538
"@tanstack/react-router": "1.135.0",
3639
"@tanstack/react-start": "1.135.0",

apps/docs/sanity.config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { codeInput } from '@sanity/code-input';
33
import { table } from '@sanity/table';
44
import { visionTool } from '@sanity/vision';
55
import { defineConfig } from 'sanity';
6+
import { defineDocuments, presentationTool } from 'sanity/presentation';
67
import { structureTool } from 'sanity/structure';
78
import { schemaTypes } from './studio/schema-types';
89

@@ -70,6 +71,22 @@ export default defineConfig({
7071
visionTool(),
7172
codeInput(),
7273
table(),
74+
presentationTool({
75+
previewUrl: {
76+
previewMode: {
77+
enable: '/api/preview-mode/enable',
78+
disable: '/api/preview-mode/disable',
79+
},
80+
},
81+
resolve: {
82+
mainDocuments: defineDocuments([
83+
{
84+
route: '/komponenter/:slug',
85+
filter: `_type == "component" && slug.current == $slug`,
86+
},
87+
]),
88+
},
89+
}),
7390
],
7491
schema: {
7592
types: schemaTypes,

0 commit comments

Comments
 (0)