-
+
+
) : (
copy
)}
diff --git a/docs/app/(site)/enterprise/page.tsx b/docs/app/(site)/enterprise/page.tsx
new file mode 100644
index 00000000000..81a361d2813
--- /dev/null
+++ b/docs/app/(site)/enterprise/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Keystone for Enterprise',
+ description:
+ 'Discover how Keystone’s Admin UI is a natural extension of how you work in code, and has the flexibility you need to enable content creatives.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/pages/for-content-management.tsx b/docs/app/(site)/for-content-management/page-client.tsx
similarity index 90%
rename from docs/pages/for-content-management.tsx
rename to docs/app/(site)/for-content-management/page-client.tsx
index a1168e6c668..ed2041f4fdb 100644
--- a/docs/pages/for-content-management.tsx
+++ b/docs/app/(site)/for-content-management/page-client.tsx
@@ -1,39 +1,35 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { Highlight } from '../components/primitives/Highlight'
-import { MWrapper } from '../components/content/MWrapper'
-import { Section, SideBySideSection } from '../components/content/Section'
-import { Button } from '../components/primitives/Button'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Section, SideBySideSection } from '../../../components/content/Section'
+import { Button } from '../../../components/primitives/Button'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { ArrowR } from '../../../components/icons/ArrowR'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Page } from '../../../components/Page'
-import dsGeneration from '../public/assets/ds-generation.png'
-import contentManagement1 from '../public/assets/content-management-1.png'
-import contentManagement2 from '../public/assets/content-management-2.png'
-import contentManagement3 from '../public/assets/content-management-3.png'
-import contentManagement4 from '../public/assets/content-management-4.png'
-import { EndCta } from '../components/content/EndCta'
+import dsGeneration from '../../../public/assets/ds-generation.png'
+import contentManagement1 from '../../../public/assets/content-management-1.png'
+import contentManagement2 from '../../../public/assets/content-management-2.png'
+import contentManagement3 from '../../../public/assets/content-management-3.png'
+import contentManagement4 from '../../../public/assets/content-management-4.png'
+import { EndCta } from '../../../components/content/EndCta'
export default function ForOrganisations () {
const mq = useMediaQuery()
return (
-
+ Keystone for content management
diff --git a/docs/app/(site)/for-content-management/page.tsx b/docs/app/(site)/for-content-management/page.tsx
new file mode 100644
index 00000000000..29999fd0b78
--- /dev/null
+++ b/docs/app/(site)/for-content-management/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeystoneJS for Content Management',
+ description:
+ 'Discover how Keystone’s Admin UI is a natural extension of how you work in code, and has the flexibility you need to enable content creatives.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/pages/for-developers.tsx b/docs/app/(site)/for-developers/page-client.tsx
similarity index 90%
rename from docs/pages/for-developers.tsx
rename to docs/app/(site)/for-developers/page-client.tsx
index 8e4c1724451..c27772a88a4 100644
--- a/docs/pages/for-developers.tsx
+++ b/docs/app/(site)/for-developers/page-client.tsx
@@ -1,48 +1,44 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { CommunityCta } from '../components/content/CommunityCta'
-import { FrontEndLogos } from '../components/icons/FrontEndLogos'
-import { Highlight } from '../components/primitives/Highlight'
-import { Relational } from '../components/icons/Relational'
-import { TweetBox } from '../components/content/TweetBox'
-import { MWrapper } from '../components/content/MWrapper'
-import { Typescript } from '../components/icons/Typescript'
-import { Section, SideBySideSection } from '../components/content/Section'
-import { CodeBox } from '../components/content/CodeBox'
-import { Button } from '../components/primitives/Button'
-import { EndCta } from '../components/content/EndCta'
-import { Postgres } from '../components/icons/Postgres'
-import { Emoji } from '../components/primitives/Emoji'
-import { GraphQl } from '../components/icons/GraphQl'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Nextjs } from '../components/icons/Nextjs'
-import { Shield } from '../components/icons/Shield'
-import { Prisma } from '../components/icons/Prisma'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Cli } from '../components/icons/Cli'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { CommunityCta } from '../../../components/content/CommunityCta'
+import { FrontEndLogos } from '../../../components/icons/FrontEndLogos'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { Relational } from '../../../components/icons/Relational'
+import { TweetBox } from '../../../components/content/TweetBox'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Typescript } from '../../../components/icons/Typescript'
+import { Section, SideBySideSection } from '../../../components/content/Section'
+import { CodeBox } from '../../../components/content/CodeBox'
+import { Button } from '../../../components/primitives/Button'
+import { EndCta } from '../../../components/content/EndCta'
+import { Postgres } from '../../../components/icons/Postgres'
+import { Emoji } from '../../../components/primitives/Emoji'
+import { GraphQl } from '../../../components/icons/GraphQl'
+import { Type } from '../../../components/primitives/Type'
+import { ArrowR } from '../../../components/icons/ArrowR'
+import { Nextjs } from '../../../components/icons/Nextjs'
+import { Shield } from '../../../components/icons/Shield'
+import { Prisma } from '../../../components/icons/Prisma'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Cli } from '../../../components/icons/Cli'
+import { Page } from '../../../components/Page'
-import editorStorytelling from '../public/assets/editor-storytelling.png'
-import richTextEditor from '../public/assets/rich-text-editor.png'
+import editorStorytelling from '../../../public/assets/editor-storytelling.png'
+import richTextEditor from '../../../public/assets/rich-text-editor.png'
export default function ForDevelopers () {
const mq = useMediaQuery()
return (
-
+ Keystone for developers
@@ -65,7 +61,7 @@ export default function ForDevelopers () {
marginTop: '2.5rem',
})}
>
-
+
+}
diff --git a/docs/pages/for-organisations.tsx b/docs/app/(site)/for-organisations/page-client.tsx
similarity index 90%
rename from docs/pages/for-organisations.tsx
rename to docs/app/(site)/for-organisations/page-client.tsx
index 198bfa580af..b332d8c8782 100644
--- a/docs/pages/for-organisations.tsx
+++ b/docs/app/(site)/for-organisations/page-client.tsx
@@ -1,37 +1,33 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { Highlight } from '../components/primitives/Highlight'
-import { ClientLogos } from '../components/icons/ClientLogos'
-import { TweetBox } from '../components/content/TweetBox'
-import { MWrapper } from '../components/content/MWrapper'
-import { Section } from '../components/content/Section'
-import { Thinkmill } from '../components/icons/Thinkmill'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { Pill } from '../components/content/Pill'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
+import { useMediaQuery } from '../../../lib/media'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { ClientLogos } from '../../../components/icons/ClientLogos'
+import { TweetBox } from '../../../components/content/TweetBox'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Section } from '../../../components/content/Section'
+import { Thinkmill } from '../../../components/icons/Thinkmill'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { Pill } from '../../../components/content/Pill'
+import { Tick } from '../../../components/icons/Tick'
+import { Page } from '../../../components/Page'
-import editor from '../public/assets/editor.png'
-import dataIntegrity from '../public/assets/data-integrity.png'
-import { EndCta } from '../components/content/EndCta'
+import editor from '../../../public/assets/editor.png'
+import dataIntegrity from '../../../public/assets/data-integrity.png'
+import { EndCta } from '../../../components/content/EndCta'
export default function ForOrganisations () {
const mq = useMediaQuery()
return (
-
+ Keystone for organisations
diff --git a/docs/app/(site)/for-organisations/page.tsx b/docs/app/(site)/for-organisations/page.tsx
new file mode 100644
index 00000000000..1a34b44c0e2
--- /dev/null
+++ b/docs/app/(site)/for-organisations/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeystoneJS for Organisations',
+ description:
+ 'Discover how Keystone’s flexibility lets organisations scale fast and sustainably with a backend that can be shaped to any business logic.',
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/app/(site)/layout-client.tsx b/docs/app/(site)/layout-client.tsx
new file mode 100644
index 00000000000..39e4c8a0d64
--- /dev/null
+++ b/docs/app/(site)/layout-client.tsx
@@ -0,0 +1,211 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { CacheProvider } from '@emotion/react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { useContext, useEffect, useMemo, useState } from 'react'
+import createCache from '@emotion/cache'
+import Script from 'next/script'
+import { Global, css } from '@emotion/react'
+
+import { proseStyles } from '../../lib/prose-lite'
+import { Theme } from '../../components/Theme'
+import { NavContextProvider } from '../../components/docs/Navigation'
+import { SkipLinks } from '../../components/SkipLinks'
+import { createContext } from 'react'
+
+function getTheme () {
+ const isSystemColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)').matches
+ const localStorageTheme = localStorage.theme
+ if ((!localStorageTheme && isSystemColorSchemeDark) || localStorageTheme === 'dark') {
+ return 'dark'
+ }
+ return 'light'
+}
+
+const ThemeContext = createContext({
+ theme: 'light' as 'dark' | 'light',
+ setTheme: (theme: 'dark' | 'light') => {},
+})
+
+export function useThemeContext () {
+ return useContext(ThemeContext)
+}
+
+export function Html (props: { children: React.ReactNode}) {
+ const [theme, setTheme] = useState<'light' | 'dark'>(() => {
+ if (typeof window === 'undefined') return 'light'
+ return getTheme()
+ })
+ useEffect(() => {
+ const listener = () => {
+ setTheme(getTheme())
+ }
+ const match = window.matchMedia('(prefers-color-scheme: dark)')
+ match.addEventListener('change', listener)
+ return () => {
+ match.removeEventListener('change', listener)
+ }
+ }, [])
+ const context = useMemo(() => ({
+ theme,
+ setTheme (theme:'dark' | 'light') {
+ setTheme((theme) => (theme === 'dark' ? 'light' : 'dark'))
+ localStorage.setItem('theme', theme)
+ }
+ }), [theme, setTheme])
+ return {props.children}
+}
+
+export default function RootLayoutClient ({ children }: { children: React.ReactNode }) {
+ const [{ cache, flush }] = useState(() => {
+ const cache = createCache({ key: 'my' })
+ cache.compat = true
+ const prevInsert = cache.insert
+ let inserted: string[] = []
+ cache.insert = (...args) => {
+ const serialized = args[1]
+ if (cache.inserted[serialized.name] === undefined) {
+ inserted.push(serialized.name)
+ }
+ return prevInsert(...args)
+ }
+ const flush = () => {
+ const prevInserted = inserted
+ inserted = []
+ return prevInserted
+ }
+ return { cache, flush }
+ })
+
+ useServerInsertedHTML(() => {
+ const names = flush()
+ if (names.length === 0) return null
+ let styles = ''
+ for (const name of names) {
+ styles += cache.inserted[name]
+ }
+ return (
+
+ )
+ })
+
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+ )
+}
diff --git a/docs/app/(site)/layout.tsx b/docs/app/(site)/layout.tsx
new file mode 100644
index 00000000000..f2cb4e4fd83
--- /dev/null
+++ b/docs/app/(site)/layout.tsx
@@ -0,0 +1,107 @@
+import type { Metadata } from 'next'
+import RootLayoutClient, { Html } from './layout-client'
+import { siteBaseUrl } from '../../lib/og-util'
+
+const defaultTitle = 'KeystoneJS: The superpowered Node.js Headless CMS for developers'
+const defaultDescription =
+ 'Build faster and scale further with the programmable open source GraphQL API back-end for structured content projects.'
+
+export const metadata: Metadata = {
+ metadataBase: new URL(siteBaseUrl),
+ title: defaultTitle,
+ description: defaultDescription,
+ icons: {
+ apple: '/apple-touch-icon.png',
+ icon: [
+ { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
+ { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
+ ],
+ shortcut: '/favicon.ico',
+ },
+ openGraph: {
+ title: defaultTitle,
+ siteName: defaultTitle,
+ description: defaultDescription,
+ type: 'website',
+ locale: 'en',
+ images: [
+ {
+ url: '/og-image-landscape.png',
+ width: 761,
+ height: 410,
+ alt: defaultDescription,
+ },
+ ],
+ },
+ twitter: {
+ title: defaultTitle,
+ description: defaultDescription,
+ card: 'summary_large_image',
+ images: [
+ {
+ url: '/og-image-landscape.png',
+ width: 761,
+ height: 410,
+ alt: defaultTitle,
+ },
+ ],
+ },
+ other: {
+ 'msapplication-TileColor': '#2684FF',
+ 'msapplication-config': '/browserconfig.xml',
+ },
+}
+
+export default function RootLayout ({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/docs/pages/404.tsx b/docs/app/(site)/not-found.tsx
similarity index 70%
rename from docs/pages/404.tsx
rename to docs/app/(site)/not-found.tsx
index 151825598b7..addcf907b6e 100644
--- a/docs/pages/404.tsx
+++ b/docs/app/(site)/not-found.tsx
@@ -1,11 +1,12 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-import { useRouter } from 'next/router'
+/** @jsxImportSource @emotion/react */
-import { Highlight } from '../components/primitives/Highlight'
-import { Type } from '../components/primitives/Type'
-import { Page } from '../components/Page'
+'use client'
+
+import { usePathname } from 'next/navigation'
+
+import { Highlight } from '../../components/primitives/Highlight'
+import { Type } from '../../components/primitives/Type'
+import { Page } from '../../components/Page'
function ConstructionIllustration () {
return (
@@ -34,11 +35,16 @@ function ConstructionIllustration () {
// see https://github.com/keystonejs/keystone/pull/6411#issuecomment-906085389
const v5PathList = ['/tutorials', '/guides', '/keystonejs', '/api', '/discussions']
+export const metadata = {
+ title: 'Page Not Found (404)',
+ description: 'The page you are looking for could not be found.',
+}
+
export default function NotFoundPage () {
- const { asPath } = useRouter()
- const tryV5Link = v5PathList.some(x => asPath.startsWith(x))
+ const pathname = usePathname()
+ const tryV5Link = v5PathList.some((x) => pathname.startsWith(x))
return (
-
+
If you were looking for a page in the Keystone 5 docs, try{' '}
- https://v5.keystonejs.com{asPath}.
+ https://v5.keystonejs.com{pathname}
+ .
) : null}
diff --git a/docs/pages/index.tsx b/docs/app/(site)/page-client.tsx
similarity index 91%
rename from docs/pages/index.tsx
rename to docs/app/(site)/page-client.tsx
index e8b725978d7..aa60bf23e1c 100644
--- a/docs/pages/index.tsx
+++ b/docs/app/(site)/page-client.tsx
@@ -1,55 +1,51 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Image from 'next/image'
import Link from 'next/link'
-import { useMediaQuery } from '../lib/media'
-import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../components/content/CodeWindow'
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { CommunityCta } from '../components/content/CommunityCta'
-import { FrontEndLogos } from '../components/icons/FrontEndLogos'
-import { Highlight } from '../components/primitives/Highlight'
-import { WhyKeystone } from '../components/icons/WhyKeystone'
-import { MWrapper } from '../components/content/MWrapper'
-import { Relational } from '../components/icons/Relational'
-import { TweetBox } from '../components/content/TweetBox'
-import { Typescript } from '../components/icons/Typescript'
-import { Code as SourceCode, InlineCode } from '../components/primitives/Code'
-import { Automated } from '../components/icons/Automated'
-import { Section } from '../components/content/Section'
-import { Thinkmill } from '../components/icons/Thinkmill'
-import { CodeBox } from '../components/content/CodeBox'
-import { PillCta } from '../components/content/PillCta'
-import { Migration } from '../components/icons/Migration'
-import { Button } from '../components/primitives/Button'
-import { EndCta } from '../components/content/EndCta'
-import { Emoji } from '../components/primitives/Emoji'
-import { Updates } from '../components/icons/Updates'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Custom } from '../components/icons/Custom'
-import { Filter } from '../components/icons/Filter'
-import { Shield } from '../components/icons/Shield'
-import { Watch } from '../components/icons/Watch'
-import { Tick } from '../components/icons/Tick'
-import { Page } from '../components/Page'
-import { Organization } from '../components/icons/Organization'
-import { Content } from '../components/icons/Content'
-import { Code } from '../components/icons/Code'
-import contentEditorMockui from '../public/assets/content-editor-mockui.png'
-import docEditorHome from '../public/assets/doc-editor-home.png'
-import deployTargets from '../public/assets/deploy-targets.png'
+import { useMediaQuery } from '../../lib/media'
+import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../../components/content/CodeWindow'
+import { IntroWrapper, IntroHeading, IntroLead } from '../../components/content/Intro'
+import { CommunityCta } from '../../components/content/CommunityCta'
+import { FrontEndLogos } from '../../components/icons/FrontEndLogos'
+import { Highlight } from '../../components/primitives/Highlight'
+import { WhyKeystone } from '../../components/icons/WhyKeystone'
+import { MWrapper } from '../../components/content/MWrapper'
+import { Relational } from '../../components/icons/Relational'
+import { TweetBox } from '../../components/content/TweetBox'
+import { Typescript } from '../../components/icons/Typescript'
+import { Code as SourceCode, InlineCode } from '../../components/primitives/Code'
+import { Automated } from '../../components/icons/Automated'
+import { Section } from '../../components/content/Section'
+import { Thinkmill } from '../../components/icons/Thinkmill'
+import { CodeBox } from '../../components/content/CodeBox'
+import { PillCta } from '../../components/content/PillCta'
+import { Migration } from '../../components/icons/Migration'
+import { Button } from '../../components/primitives/Button'
+import { EndCta } from '../../components/content/EndCta'
+import { Emoji } from '../../components/primitives/Emoji'
+import { Updates } from '../../components/icons/Updates'
+import { Type } from '../../components/primitives/Type'
+import { ArrowR } from '../../components/icons/ArrowR'
+import { Custom } from '../../components/icons/Custom'
+import { Filter } from '../../components/icons/Filter'
+import { Shield } from '../../components/icons/Shield'
+import { Watch } from '../../components/icons/Watch'
+import { Tick } from '../../components/icons/Tick'
+import { Page } from '../../components/Page'
+import { Organization } from '../../components/icons/Organization'
+import { Content } from '../../components/icons/Content'
+import { Code } from '../../components/icons/Code'
+import contentEditorMockui from '../../public/assets/content-editor-mockui.png'
+import docEditorHome from '../../public/assets/doc-editor-home.png'
+import deployTargets from '../../public/assets/deploy-targets.png'
-export default function IndexPage () {
+export default function PageClient () {
const mq = useMediaQuery()
return (
-
+
@@ -57,12 +53,12 @@ export default function IndexPage () {
Keystone helps you build faster and scale further than any other CMS or App Framework.
- Just describe your schema, and get a powerful GraphQL API & beautiful Management UI for
+ Describe your schema, and you get a powerful GraphQL API & beautiful Management UI for
content and data.
- No boilerplate or bootstrapping – just elegant APIs to help you ship the code that
- matters without sacrificing the flexibility or power of a bespoke back-end.
+ No boilerplate or bootstrapping – only elegant APIs to help you ship the code that
+ matters, without sacrificing the flexibility or power of a bespoke back-end.
@@ -75,7 +71,7 @@ export default function IndexPage () {
marginTop: '2.5rem',
})}
>
-
+
+
+
+
)
}
-export function Page ({
- children,
- title,
- description,
- ogImage,
-}: {
- children: ReactNode
- title: string
- description: string
- ogImage?: string
-}) {
- const metaTitle = title ? `${title} - Keystone 6` : `Keystone 6`
+export function Page ({ children }: { children: ReactNode }) {
return (
-
- }
-
- return
-}
+import { useThemeContext } from '../app/(site)/layout-client'
export function ThemeToggle (props: HTMLAttributes) {
/*
@@ -22,29 +14,14 @@ export function ThemeToggle (props: HTMLAttributes) {
even if the theme is dark mode based on system preference.
So we render the toggle only on the client
*/
- const [theme, setTheme] = useState(null)
-
- useEffect(() => {
- const currentTheme = document.documentElement.getAttribute('data-theme') as 'light' | 'dark'
- setTheme(currentTheme)
- }, [setTheme])
-
- const handleThemeChange = () => {
- const newTheme = theme === 'dark' ? 'light' : 'dark'
- if (newTheme === 'dark') {
- document.documentElement.setAttribute('data-theme', 'dark')
- } else {
- document.documentElement.setAttribute('data-theme', 'light')
- }
- setTheme(newTheme)
- localStorage.setItem('theme', newTheme)
- }
+ const theme = useThemeContext()
return (
)
diff --git a/docs/components/content/AdvancedReactCta.tsx b/docs/components/content/AdvancedReactCta.tsx
index 8574136c0e9..dc1def6a2ec 100644
--- a/docs/components/content/AdvancedReactCta.tsx
+++ b/docs/components/content/AdvancedReactCta.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import type { HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import Image from 'next/image'
import wesBosCta from '../../public/assets/wesbos-cta.jpg'
diff --git a/docs/components/content/CodeBox.tsx b/docs/components/content/CodeBox.tsx
index bd10b71cb8b..9ef2fc63685 100644
--- a/docs/components/content/CodeBox.tsx
+++ b/docs/components/content/CodeBox.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes, useEffect, useState } from 'react'
-import { jsx } from '@emotion/react'
import copy from 'clipboard-copy'
import { CheckIcon } from '@keystone-ui/icons/icons/CheckIcon'
diff --git a/docs/components/content/CodeWindow.tsx b/docs/components/content/CodeWindow.tsx
index a764806cc46..9795bf8ef2a 100644
--- a/docs/components/content/CodeWindow.tsx
+++ b/docs/components/content/CodeWindow.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import type { HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/content/CommunityCta.tsx b/docs/components/content/CommunityCta.tsx
index 5134bf8e3ea..b35eabe5770 100644
--- a/docs/components/content/CommunityCta.tsx
+++ b/docs/components/content/CommunityCta.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import type { HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import Image from 'next/image'
import communityMap from '../../public/assets/community-map.png'
diff --git a/docs/components/content/CustomerCard.tsx b/docs/components/content/CustomerCard.tsx
index a094e15a6f5..784f1f38c7e 100644
--- a/docs/components/content/CustomerCard.tsx
+++ b/docs/components/content/CustomerCard.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import type { HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import { Type } from '../primitives/Type'
import { type IconProps } from '../icons/util'
diff --git a/docs/components/content/EndCta.tsx b/docs/components/content/EndCta.tsx
index 978d31758f6..d7cba1ac817 100644
--- a/docs/components/content/EndCta.tsx
+++ b/docs/components/content/EndCta.tsx
@@ -1,6 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
import { Highlight } from '../primitives/Highlight'
@@ -73,7 +73,7 @@ export function EndCta ({ grad = 'grad1', ...props }: EndCtaProps) {
-
+
diff --git a/docs/components/docs/TableOfContents.tsx b/docs/components/docs/TableOfContents.tsx
index 9a980797cbc..3b3d2c756f8 100644
--- a/docs/components/docs/TableOfContents.tsx
+++ b/docs/components/docs/TableOfContents.tsx
@@ -1,7 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import { useState, useEffect } from 'react'
-import { jsx } from '@emotion/react'
import { useMediaQuery } from '../../lib/media'
import { Type } from '../primitives/Type'
diff --git a/docs/components/docs/WalkthroughsList.tsx b/docs/components/docs/WalkthroughsList.tsx
deleted file mode 100644
index dffe232f2c8..00000000000
--- a/docs/components/docs/WalkthroughsList.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
-import { Well } from '../primitives/Well'
-import { useMediaQuery } from '../../lib/media'
-import { InlineCode } from '../../components/primitives/Code'
-
-export function Walkthroughs () {
- const mq = useMediaQuery()
- return (
-
-
-
- Take a tour of Keystone in minutes with our CLI starter project
-
-
-
-
- Get Keystone up and running with your first content type
-
-
- Connect two content types and learn how to configure the appearance of field inputs
-
-
- Support publishing needs with Keystone's select and{' '}
- timestamp fields
-
-
- Add sessions, password protection, and a sign-in screen to your Keystone app
-
-
- Add a powerful document field to your app and learn how to
- configure it to meet your needs
-
-
-
- )
-}
diff --git a/docs/components/docs/docs-navigation/client.tsx b/docs/components/docs/docs-navigation/client.tsx
new file mode 100644
index 00000000000..b9139fef6b4
--- /dev/null
+++ b/docs/components/docs/docs-navigation/client.tsx
@@ -0,0 +1,48 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { type NavigationMap } from '.'
+import { Badge } from '../../primitives/Badge'
+import { NavItem, NavSection } from '../Navigation'
+
+function KeystaticNavItem ({ item }: { item: NonNullable[number]['items'][number] }) {
+ return (
+
+ {item.label}
+ {/* Status badges */}
+ {item.status !== 'default' && ' '}
+ {(item.status === 'new' || item.status === 'updated') && (
+ {item.status}
+ )}
+
+ )
+}
+
+export function DocsNavigationClient ({ navigationMap }: { navigationMap: NavigationMap }) {
+ if (!navigationMap) return null
+
+ return (
+
+ )
+}
diff --git a/docs/components/docs/docs-navigation/index.tsx b/docs/components/docs/docs-navigation/index.tsx
new file mode 100644
index 00000000000..0536d325215
--- /dev/null
+++ b/docs/components/docs/docs-navigation/index.tsx
@@ -0,0 +1,33 @@
+import { reader } from '../../../keystatic/reader'
+import { DocsNavigationClient } from './client'
+
+export type NavigationMap = Awaited>
+
+export async function getNavigationMap () {
+ const navigation = await reader.singletons.navigation.read()
+ const pages = await reader.collections.docs.all()
+
+ const pagesBySlug = Object.fromEntries(pages.map((page) => [page.slug, page]))
+
+ const navigationMap = navigation?.navGroups.map(({ groupName, items }) => ({
+ groupName,
+ items: items.map(({ label, link, status }) => {
+ const { discriminant, value } = link
+ const page = discriminant === 'page' && value ? pagesBySlug[value] : null
+ const url = discriminant === 'url' ? value : `/docs/${page?.slug}`
+
+ return {
+ label: label || page?.entry.title || '',
+ href: url || '',
+ status,
+ }
+ }),
+ }))
+
+ return navigationMap
+}
+
+export async function DocsNavigation () {
+ const navigationMap = await getNavigationMap()
+ return
+}
\ No newline at end of file
diff --git a/docs/components/docs/featured-docs/client.tsx b/docs/components/docs/featured-docs/client.tsx
new file mode 100644
index 00000000000..74a03c48507
--- /dev/null
+++ b/docs/components/docs/featured-docs/client.tsx
@@ -0,0 +1,79 @@
+'use client'
+
+import { Well } from '../../primitives/Well'
+import { Type } from '../../primitives/Type'
+
+import { Markdoc } from '../../Markdoc'
+import { FeaturedCard, FullWidthCardContainer, SplitCardContainer } from '../FeaturedCard'
+import { type FeaturedDocsMap } from '../../../keystatic/get-featured-docs-map'
+
+export function FeaturedDocsClient ({ featuredDocsMap }: { featuredDocsMap: FeaturedDocsMap }) {
+ if (!featuredDocsMap) return null
+
+ // Separating the first group/item for featured UI treatment
+ const [firstGroup, ...restGroups] = featuredDocsMap
+ const [featuredItem, ...restItems] = firstGroup.items
+
+ return (
+ <>
+ {/* First Group */}
+
+ {firstGroup.groupName}
+
+
+ {firstGroup.groupDescription.children.map((child, i) => (
+
+ ))}
+
+
+ {/* Featured Item */}
+ {!!featuredItem.description && (
+
+
+ {featuredItem.description.children.map((child, i) => (
+
+ ))}
+
+
+ )}
+ {/* Remaining items of the first group */}
+
+ {restItems.map((item, i) => (
+
+ ))}
+
+
+ {/* Remaining groups */}
+ {restGroups.map((group, i) => (
+