diff --git a/.changeset/add-isnullable-multiselect.md b/.changeset/add-isnullable-multiselect.md
deleted file mode 100644
index edf3088e6da..00000000000
--- a/.changeset/add-isnullable-multiselect.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': minor
----
-
-Add `db.isNullable` support for multiselect field type, defaults to false
diff --git a/.changeset/config.json b/.changeset/config.json
index a96ace8c24c..24654475a0b 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -5,6 +5,9 @@
"baseBranch": "main",
"linked": [],
"access": "public",
+ "privatePackages": {
+ "version": false
+ },
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
diff --git a/.changeset/contributors.json b/.changeset/contributors.json
index 895f18cd532..665363c39a3 100644
--- a/.changeset/contributors.json
+++ b/.changeset/contributors.json
@@ -38,6 +38,7 @@
"dcousens",
"dependabot",
"dependabot[bot]",
+ "direisc",
"dominikwilkowski",
"duidae",
"emmatown",
diff --git a/.changeset/fix-bigint-validation.md b/.changeset/fix-bigint-validation.md
deleted file mode 100644
index 2c49d9e94d8..00000000000
--- a/.changeset/fix-bigint-validation.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': patch
----
-
-Fix bigInt field type to throw if `defaultValue: { kind: 'autoincrement' }` and `validation.isRequired` is set
diff --git a/.changeset/fix-hide-ui.md b/.changeset/fix-hide-ui.md
deleted file mode 100644
index a0fa2c69c6f..00000000000
--- a/.changeset/fix-hide-ui.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': patch
----
-
-Fix `list.ui.hide*` defaulting to false when GraphQL is omitted
diff --git a/.changeset/fix-keystone-prisma.md b/.changeset/fix-keystone-prisma.md
deleted file mode 100644
index 4b71b10fec5..00000000000
--- a/.changeset/fix-keystone-prisma.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': patch
----
-
-Fix `keystone prisma ...` not returning the same error code as the Prisma engine
diff --git a/.changeset/fix-uuid-rel.md b/.changeset/fix-uuid-rel.md
deleted file mode 100644
index dc50719a017..00000000000
--- a/.changeset/fix-uuid-rel.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': patch
----
-
-Fix malformed uuid's from breaking relationship filters when using POSTGRESQL
diff --git a/.changeset/light-lamps-eat.md b/.changeset/light-lamps-eat.md
deleted file mode 100644
index 208bfb90424..00000000000
--- a/.changeset/light-lamps-eat.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/core': minor
----
-
-Add exports for internal AdminUI pagination components `Pagination`, `PaginationLabel` and `usePaginationParams` for use in custom pages
diff --git a/.changeset/split-document-field.md b/.changeset/split-document-field.md
deleted file mode 100644
index 19265a633ed..00000000000
--- a/.changeset/split-document-field.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@keystone-6/fields-document': minor
----
-
-Fix `@keystone-6/fields-document` package breaking when compiling in SSR environments (#8717)
diff --git a/.changeset/update-field-hooks.md b/.changeset/update-field-hooks.md
deleted file mode 100644
index b0ea3bd7d5f..00000000000
--- a/.changeset/update-field-hooks.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@keystone-6/core": patch
----
-
-Update built-in fields to use newer validate hook syntax
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000000..6c2b9be4c4f
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+link-workspace-packages=true
+prefer-workspace-packages=true
diff --git a/LICENSE b/LICENSE
index 2762e648976..8f0e5cd3917 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 Thinkmill Labs Pty Ltd
+Copyright (c) 2024 Thinkmill Labs Pty Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 8e401a59fdd..54b63a1a1e9 100644
--- a/README.md
+++ b/README.md
@@ -87,4 +87,4 @@ For vulnerability reporting, please refer to our [security policy](/SECURITY.md)
-Copyright (c) 2023 [Thinkmill Labs](https://www.thinkmill.com.au/labs?utm_campaign=keystone-github) Pty Ltd. Licensed under the MIT License.
+Copyright (c) 2024 [Thinkmill Labs](https://www.thinkmill.com.au/labs?utm_campaign=keystone-github) Pty Ltd. Licensed under the MIT License.
diff --git a/design-system/packages/button/package.json b/design-system/packages/button/package.json
index 4566143100b..2522f425f13 100644
--- a/design-system/packages/button/package.json
+++ b/design-system/packages/button/package.json
@@ -12,14 +12,14 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:"
+ "@types/react": "^18.3.3"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@keystone-ui/core": "workspace:^",
"@keystone-ui/icons": "workspace:^",
"@keystone-ui/loading": "workspace:^",
- "react": "catalog:"
+ "react": "^18.3.1"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/design-system/packages/button"
}
diff --git a/design-system/packages/core/package.json b/design-system/packages/core/package.json
index e4e0d5d09eb..5fb189129e1 100644
--- a/design-system/packages/core/package.json
+++ b/design-system/packages/core/package.json
@@ -12,13 +12,13 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:",
- "react": "catalog:",
- "react-dom": "catalog:"
+ "@types/react": "^18.3.3",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"peerDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
diff --git a/design-system/packages/fields/package.json b/design-system/packages/fields/package.json
index 5fe7a755d5f..a4a6a86284b 100644
--- a/design-system/packages/fields/package.json
+++ b/design-system/packages/fields/package.json
@@ -12,7 +12,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:"
+ "@types/react": "^18.3.3"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
@@ -20,9 +20,9 @@
"@keystone-ui/icons": "workspace:^",
"@keystone-ui/popover": "workspace:^",
"date-fns": "^3.0.0",
- "react": "catalog:",
+ "react": "^18.3.1",
"react-day-picker": "^8.0.4",
- "react-dom": "catalog:",
+ "react-dom": "^18.3.1",
"react-focus-lock": "^2.7.1",
"react-select": "^5.2.1"
},
diff --git a/design-system/packages/icons/package.json b/design-system/packages/icons/package.json
index e96ee372636..3d200806747 100644
--- a/design-system/packages/icons/package.json
+++ b/design-system/packages/icons/package.json
@@ -1162,10 +1162,10 @@
"@svgr/core": "^8.0.0",
"@svgr/plugin-jsx": "^8.0.0",
"@svgr/plugin-svgo": "^8.0.0",
- "@types/react": "catalog:",
+ "@types/react": "^18.3.3",
"feather-icons": "^4.28.0",
"globby": "^14.0.0",
- "react": "catalog:",
+ "react": "^18.3.1",
"to-pascal-case": "^1.0.0"
},
"dependencies": {
@@ -1173,7 +1173,7 @@
"@keystone-ui/core": "workspace:^"
},
"peerDependencies": {
- "react": "catalog:"
+ "react": "^18.3.1"
},
"preconstruct": {
"entrypoints": [
diff --git a/design-system/packages/loading/package.json b/design-system/packages/loading/package.json
index 9cd72e5df7a..161bef8e560 100644
--- a/design-system/packages/loading/package.json
+++ b/design-system/packages/loading/package.json
@@ -12,12 +12,12 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:"
+ "@types/react": "^18.3.3"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@keystone-ui/core": "workspace:^",
- "react": "catalog:"
+ "react": "^18.3.1"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/design-system/packages/loading"
}
diff --git a/design-system/packages/modals/package.json b/design-system/packages/modals/package.json
index 7ad7a70e4f4..4c171bee16b 100644
--- a/design-system/packages/modals/package.json
+++ b/design-system/packages/modals/package.json
@@ -12,15 +12,15 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:",
+ "@types/react": "^18.3.3",
"@types/react-transition-group": "4.4.10"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@keystone-ui/button": "workspace:^",
"@keystone-ui/core": "workspace:^",
- "react": "catalog:",
- "react-dom": "catalog:",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"react-focus-lock": "^2.7.1",
"react-remove-scroll": "^2.4.3",
"react-transition-group": "^4.4.2"
diff --git a/design-system/packages/notice/package.json b/design-system/packages/notice/package.json
index a222ed7e3c8..bb87940b905 100644
--- a/design-system/packages/notice/package.json
+++ b/design-system/packages/notice/package.json
@@ -12,14 +12,14 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:"
+ "@types/react": "^18.3.3"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@keystone-ui/button": "workspace:^",
"@keystone-ui/core": "workspace:^",
"@keystone-ui/icons": "workspace:^",
- "react": "catalog:"
+ "react": "^18.3.1"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/design-system/packages/notice"
}
diff --git a/design-system/packages/options/package.json b/design-system/packages/options/package.json
index 38022dd136d..38e4ec075b4 100644
--- a/design-system/packages/options/package.json
+++ b/design-system/packages/options/package.json
@@ -12,12 +12,12 @@
"./package.json": "./package.json"
},
"peerDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"devDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
diff --git a/design-system/packages/pill/package.json b/design-system/packages/pill/package.json
index a6a41a74e62..4cfa2e947f7 100644
--- a/design-system/packages/pill/package.json
+++ b/design-system/packages/pill/package.json
@@ -12,10 +12,10 @@
"./package.json": "./package.json"
},
"peerDependencies": {
- "react": "catalog:"
+ "react": "^18.3.1"
},
"devDependencies": {
- "react": "catalog:"
+ "react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
diff --git a/design-system/packages/popover/package.json b/design-system/packages/popover/package.json
index 2dcf0d766b6..a7164d3d0b6 100644
--- a/design-system/packages/popover/package.json
+++ b/design-system/packages/popover/package.json
@@ -12,12 +12,12 @@
"./package.json": "./package.json"
},
"peerDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"devDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
diff --git a/design-system/packages/segmented-control/package.json b/design-system/packages/segmented-control/package.json
index 40c2182a763..12fdfb60c44 100644
--- a/design-system/packages/segmented-control/package.json
+++ b/design-system/packages/segmented-control/package.json
@@ -12,15 +12,15 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:",
- "react": "catalog:"
+ "@types/react": "^18.3.3",
+ "react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@keystone-ui/core": "workspace:^"
},
"peerDependencies": {
- "react": "catalog:"
+ "react": "^18.3.1"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/design-system/packages/segmented-control"
}
diff --git a/design-system/packages/toast/package.json b/design-system/packages/toast/package.json
index 6c76869eca8..76f139fceb0 100644
--- a/design-system/packages/toast/package.json
+++ b/design-system/packages/toast/package.json
@@ -12,8 +12,8 @@
"./package.json": "./package.json"
},
"devDependencies": {
- "@types/react": "catalog:",
- "react": "catalog:"
+ "@types/react": "^18.3.3",
+ "react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
@@ -21,7 +21,7 @@
"@keystone-ui/icons": "workspace:^"
},
"peerDependencies": {
- "react": "catalog:"
+ "react": "^18.3.1"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/design-system/packages/toast"
}
diff --git a/design-system/packages/tooltip/package.json b/design-system/packages/tooltip/package.json
index e29f3351f94..e4bc853c3c5 100644
--- a/design-system/packages/tooltip/package.json
+++ b/design-system/packages/tooltip/package.json
@@ -12,12 +12,12 @@
"./package.json": "./package.json"
},
"peerDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"devDependencies": {
- "react": "catalog:",
- "react-dom": "catalog:"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
diff --git a/design-system/website/components/Navigation.tsx b/design-system/website/components/Navigation.tsx
index f93a7e9ea14..c8e52158799 100644
--- a/design-system/website/components/Navigation.tsx
+++ b/design-system/website/components/Navigation.tsx
@@ -4,7 +4,7 @@
import { Fragment, type ReactNode } from 'react'
import { jsx, useTheme } from '@keystone-ui/core'
import Link from 'next/link'
-import { useRouter } from 'next/router'
+import { usePathname } from 'next/navigation'
const Brand = () => {
const { palette } = useTheme()
@@ -37,8 +37,8 @@ const Section = ({ label, children }: SectionProps) => {
type NavItemProps = { href: string, children: ReactNode }
const NavItem = ({ href, children }: NavItemProps) => {
const { palette, radii, spacing } = useTheme()
- const router = useRouter()
- const isSelected = router.pathname === href
+ const pathname = usePathname()
+ const isSelected = pathname === href
return (
+
+ {post.title}
+
+
+
+ Published on {formattedDateStr}
+ {post.authorHandle ? (
+
+ {' '}
+ by{' '}
+
+ {post.authorName}
+
+
+ ) : (
+ by {post.authorName}
+ )}
+
+
+ {post.content.children.map((child, i) => (
+
+ ))}
+
+ )
+}
diff --git a/docs/app/(site)/blog/[post]/page.tsx b/docs/app/(site)/blog/[post]/page.tsx
new file mode 100644
index 00000000000..ac22aa6516f
--- /dev/null
+++ b/docs/app/(site)/blog/[post]/page.tsx
@@ -0,0 +1,68 @@
+import { type Tag, transform } from '@markdoc/markdoc'
+import { notFound } from 'next/navigation'
+
+import { getOgAbsoluteUrl } from '../../../../lib/og-util'
+import { reader } from '../../../../keystatic/reader'
+import { baseMarkdocConfig } from '../../../../markdoc/config'
+import { type EntryWithResolvedLinkedFiles } from '@keystatic/core/reader'
+import type keystaticConfig from '../../../../keystatic.config'
+import PageClient from './page-client'
+import { type Metadata } from 'next'
+
+export type BlogPost = NonNullable<
+ Omit<
+ EntryWithResolvedLinkedFiles<(typeof keystaticConfig)['collections']['posts']>,
+ 'content'
+ > & {
+ content: Tag
+ }
+>
+
+export default async function Page ({ params }) {
+ const post = await reader.collections.posts.read(params!.post, {
+ resolveLinkedFiles: true,
+ })
+
+ if (!post) return notFound()
+
+ return (
+
+ )
+}
+
+// Dynamic SEO page metadata
+export async function generateMetadata ({ params }): Promise {
+ const post = await reader.collections.posts.read(params!.post)
+
+ const title = post?.title ? `${post.title} - Keystone 6 Blog` : 'Keystone 6 Blog'
+
+ let ogImageUrl = post?.metaImageUrl
+ if (!ogImageUrl) {
+ ogImageUrl = getOgAbsoluteUrl({
+ title,
+ type: 'Blog',
+ })
+ }
+
+ return {
+ title,
+ description: post?.description,
+ openGraph: {
+ images: ogImageUrl,
+ },
+ }
+}
+
+// Static HTML page generation for each document page
+export async function generateStaticParams () {
+ const posts = await reader.collections.posts.list()
+ return posts.map((post) => ({ post }))
+}
diff --git a/docs/pages/blog/index.tsx b/docs/app/(site)/blog/page-client.tsx
similarity index 52%
rename from docs/pages/blog/index.tsx
rename to docs/app/(site)/blog/page-client.tsx
index c825d433b7e..503a2261ed9 100644
--- a/docs/pages/blog/index.tsx
+++ b/docs/app/(site)/blog/page-client.tsx
@@ -1,50 +1,20 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { type InferGetStaticPropsType, type GetStaticPropsResult } from 'next'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import Link from 'next/link'
-import { parse, format } from 'date-fns'
-import { jsx } from '@emotion/react'
-import { MWrapper } from '../../components/content/MWrapper'
-import { Page } from '../../components/Page'
-import { Type } from '../../components/primitives/Type'
-import { Highlight } from '../../components/primitives/Highlight'
-import { useMediaQuery } from '../../lib/media'
-import { siteBaseUrl } from '../../lib/og-util'
-import { reader } from '../../lib/keystatic-reader'
-import { type Entry } from '@keystatic/core/reader'
-import type keystaticConfig from '../../keystatic.config'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Page } from '../../../components/Page'
+import { Type } from '../../../components/primitives/Type'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { useMediaQuery } from '../../../lib/media'
-const today = new Date()
-export default function Docs (props: InferGetStaticPropsType) {
+export default function Docs ({ posts }) {
const mq = useMediaQuery()
- // reverse chronologically sorted
- const posts = props.posts
- .map(p => {
- const publishedDate = p.frontmatter.publishDate
- const parsedDate = parse(publishedDate, 'yyyy-M-d', today)
- const formattedDateStr = format(parsedDate, 'MMMM do, yyyy')
- return {
- ...p,
- frontmatter: { ...p.frontmatter },
- parsedDate: parsedDate,
- formattedDateStr: formattedDateStr,
- }
- })
- .sort((a, b) => {
- if (a.frontmatter.publishDate === b.frontmatter.publishDate) {
- return a.frontmatter.title.localeCompare(b.frontmatter.title)
- }
- return b.frontmatter.publishDate.localeCompare(a.frontmatter.publishDate)
- })
-
return (
-
+
- {posts.map(post => {
+ {posts.map((post) => {
return (
-
-
-
- {post.frontmatter.title}
-
-
+
+
+ {post.frontmatter.title}
+
{post.frontmatter.description}
@@ -157,20 +127,3 @@ export default function Docs (props: InferGetStaticPropsType
)
}
-
-export async function getStaticProps (): Promise<
- GetStaticPropsResult<{
- posts: {
- slug: string
- frontmatter: Omit, 'content'>
- }[]
- }>
-> {
- const keystaticPosts = await reader.collections.posts.all()
-
- const postMeta = keystaticPosts.map(post => ({
- slug: post.slug,
- frontmatter: { ...post.entry, content: null },
- }))
- return { props: { posts: postMeta } }
-}
diff --git a/docs/app/(site)/blog/page.tsx b/docs/app/(site)/blog/page.tsx
new file mode 100644
index 00000000000..9ed12ec9a1f
--- /dev/null
+++ b/docs/app/(site)/blog/page.tsx
@@ -0,0 +1,46 @@
+import { parse, format } from 'date-fns'
+
+import { reader } from '../../../keystatic/reader'
+import ClientPage from './page-client'
+import { type Metadata } from 'next'
+
+const today = new Date()
+
+export const metadata: Metadata = {
+ title: 'Keystone Blog',
+ description: 'Blog posts from the team maintaining Keystone.',
+ openGraph: {
+ images: '/assets/blog/the-keystone-blog-cover.png'
+ }
+}
+
+export default async function Docs () {
+ const keystaticPosts = await reader.collections.posts.all()
+
+ const transformedPosts = keystaticPosts.map((post) => ({
+ slug: post.slug,
+ frontmatter: { ...post.entry, content: null },
+ }))
+
+ // Reverse chronologically sorted
+ const sortedPosts = transformedPosts
+ .map((p) => {
+ const publishedDate = p.frontmatter.publishDate
+ const parsedDate = parse(publishedDate, 'yyyy-M-d', today)
+ const formattedDateStr = format(parsedDate, 'MMMM do, yyyy')
+ return {
+ ...p,
+ frontmatter: { ...p.frontmatter },
+ parsedDate: parsedDate,
+ formattedDateStr: formattedDateStr,
+ }
+ })
+ .sort((a, b) => {
+ if (a.frontmatter.publishDate === b.frontmatter.publishDate) {
+ return a.frontmatter.title.localeCompare(b.frontmatter.title)
+ }
+ return b.frontmatter.publishDate.localeCompare(a.frontmatter.publishDate)
+ })
+
+ return
+}
diff --git a/docs/pages/branding.tsx b/docs/app/(site)/branding/page-client.tsx
similarity index 85%
rename from docs/pages/branding.tsx
rename to docs/app/(site)/branding/page-client.tsx
index cd2b446ace7..8ee68425f55 100644
--- a/docs/pages/branding.tsx
+++ b/docs/app/(site)/branding/page-client.tsx
@@ -1,27 +1,27 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
-import { IntroWrapper, IntroHeading, IntroLead } from '../components/content/Intro'
-import { Highlight } from '../components/primitives/Highlight'
-import { MWrapper } from '../components/content/MWrapper'
-import { Download } from '../components/icons/Download'
-import { Keystone } from '../components/icons/Keystone'
-import { Type } from '../components/primitives/Type'
-import { Pill } from '../components/content/Pill'
-import { Nope } from '../components/icons/Nope'
-import { Tick } from '../components/icons/Tick'
-import { useMediaQuery } from '../lib/media'
-import { Page } from '../components/Page'
-import { Alert } from '../components/primitives/Alert'
-import { Button } from '../components/primitives/Button'
-import { ArrowR } from '../components/icons/ArrowR'
+'use client'
+
+import { IntroWrapper, IntroHeading, IntroLead } from '../../../components/content/Intro'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Download } from '../../../components/icons/Download'
+import { Keystone } from '../../../components/icons/Keystone'
+import { Type } from '../../../components/primitives/Type'
+import { Pill } from '../../../components/content/Pill'
+import { Nope } from '../../../components/icons/Nope'
+import { Tick } from '../../../components/icons/Tick'
+import { useMediaQuery } from '../../../lib/media'
+import { Page } from '../../../components/Page'
+import { Alert } from '../../../components/primitives/Alert'
+import { Button } from '../../../components/primitives/Button'
+import { ArrowR } from '../../../components/icons/ArrowR'
export default function Brand () {
const mq = useMediaQuery()
return (
-
+
Branding
diff --git a/docs/app/(site)/branding/page.tsx b/docs/app/(site)/branding/page.tsx
new file mode 100644
index 00000000000..32e07479bb4
--- /dev/null
+++ b/docs/app/(site)/branding/page.tsx
@@ -0,0 +1,10 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeytoneJS Brand',
+ description: 'Keystones brand assets and guidelines',
+}
+
+export default function Brand () {
+ return
+}
diff --git a/docs/app/(site)/docs/[...rest]/page-client.tsx b/docs/app/(site)/docs/[...rest]/page-client.tsx
new file mode 100644
index 00000000000..c5531b2b94c
--- /dev/null
+++ b/docs/app/(site)/docs/[...rest]/page-client.tsx
@@ -0,0 +1,21 @@
+'use client'
+
+import { type Document } from './page'
+
+import { Markdoc } from '../../../../components/Markdoc'
+
+import { Heading } from '../../../../components/docs/Heading'
+
+export default function PageClient ({ document }: { document: Document }) {
+ return (
+ <>
+
+ {document.title}
+
+
+ {document.content.children.map((child, i) => (
+
+ ))}
+ >
+ )
+}
diff --git a/docs/app/(site)/docs/[...rest]/page.tsx b/docs/app/(site)/docs/[...rest]/page.tsx
new file mode 100644
index 00000000000..e8a29b42bb8
--- /dev/null
+++ b/docs/app/(site)/docs/[...rest]/page.tsx
@@ -0,0 +1,57 @@
+import { notFound } from 'next/navigation'
+import { type Tag, transform } from '@markdoc/markdoc'
+
+import { reader } from '../../../../keystatic/reader'
+import { baseMarkdocConfig } from '../../../../markdoc/config'
+import { extractHeadings } from '../../../../markdoc/headings'
+import PageClient from './page-client'
+import { type EntryWithResolvedLinkedFiles } from '@keystatic/core/reader'
+import type keystaticConfig from '../../../../keystatic.config'
+import { DocsLayout } from '../../../../components/docs/DocsLayout'
+
+export type Document = NonNullable<
+ Pick<
+ EntryWithResolvedLinkedFiles<(typeof keystaticConfig)['collections']['docs']>,
+ 'title' | 'description'
+ > & {
+ content: Tag
+ }
+>
+
+export default async function DocPage ({ params }) {
+ const doc = await reader.collections.docs.read(params!.rest.join('/'), {
+ resolveLinkedFiles: true,
+ })
+ if (!doc) return notFound()
+
+ const transformedDoc: Document = {
+ ...doc,
+ content: transform(doc.content.node, baseMarkdocConfig) as Tag,
+ }
+
+ const headings = [
+ { id: 'title', depth: 1, label: transformedDoc.title },
+ ...extractHeadings(transformedDoc.content),
+ ]
+
+ return (
+
+
+
+ )
+}
+
+// Dynamic SEO page metadata
+export async function generateMetadata ({ params }) {
+ const doc = await reader.collections.docs.read(params!.rest.join('/'))
+ return {
+ title: doc?.title ? `${doc.title} - Keystone 6 Documentation` : 'Keystone 6 Documentation',
+ description: doc?.description,
+ }
+}
+
+// Static HTML page generation for each document page
+export async function generateStaticParams () {
+ const pages = await reader.collections.docs.list()
+ return pages.map((page) => ({ rest: page.split('/') }))
+}
diff --git a/docs/pages/docs/config/overview.tsx b/docs/app/(site)/docs/config/overview/page-client.tsx
similarity index 76%
rename from docs/pages/docs/config/overview.tsx
rename to docs/app/(site)/docs/config/overview/page-client.tsx
index 78606e7bee3..293146cdd57 100644
--- a/docs/pages/docs/config/overview.tsx
+++ b/docs/app/(site)/docs/config/overview/page-client.tsx
@@ -1,25 +1,17 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
-import { CommunitySlackCTA } from '../../../components/docs/CommunitySlackCTA'
-import { Type } from '../../../components/primitives/Type'
-import { Well } from '../../../components/primitives/Well'
-import { DocsPage } from '../../../components/Page'
-import { useMediaQuery } from '../../../lib/media'
+'use client'
+
+import { CommunitySlackCTA } from '../../../../../components/docs/CommunitySlackCTA'
+import { Type } from '../../../../../components/primitives/Type'
+import { Well } from '../../../../../components/primitives/Well'
+import { useMediaQuery } from '../../../../../lib/media'
export default function Docs () {
const mq = useMediaQuery()
return (
-
+ <>
Configuration Overview
@@ -62,6 +54,6 @@ export default function Docs () {
and one-time authentication tokens.
-
+ >
)
}
diff --git a/docs/app/(site)/docs/config/overview/page.tsx b/docs/app/(site)/docs/config/overview/page.tsx
new file mode 100644
index 00000000000..45367d9b252
--- /dev/null
+++ b/docs/app/(site)/docs/config/overview/page.tsx
@@ -0,0 +1,15 @@
+import { DocsLayout } from '../../../../../components/docs/DocsLayout'
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'APIs',
+ description: 'Index for Keystone’s API reference docs.',
+}
+
+export default function Docs () {
+ return (
+
+
+
+ )
+}
diff --git a/docs/app/(site)/docs/examples/page-client.tsx b/docs/app/(site)/docs/examples/page-client.tsx
new file mode 100644
index 00000000000..6c01dccecca
--- /dev/null
+++ b/docs/app/(site)/docs/examples/page-client.tsx
@@ -0,0 +1,97 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { type Tag } from '@markdoc/markdoc'
+
+import { type GroupedExamples } from './page'
+import { GitHubExamplesCTA } from '../../../../components/docs/GitHubExamplesCTA'
+import { Type } from '../../../../components/primitives/Type'
+import { FeaturedCard, SplitCardContainer } from '../../../../components/docs/FeaturedCard'
+import { useMediaQuery } from '../../../../lib/media'
+
+export default function Docs ({
+ standaloneExamples,
+ endToEndExamples,
+ deploymentExamples,
+}: GroupedExamples) {
+ return (
+ <>
+
+ Examples
+
+
+
+ A growing collection of projects you can run locally to learn more about Keystone’s
+ capabilities. Each example includes documentation explaining the how and why. Use them as a
+ reference for best practice, and a jumping off point when adding features to your own
+ Keystone project.
+
+
+
+
+
+ Standalone Examples
+
+
+
+ Standalone examples demonstrate how a particular feature works or how to solve a problem
+ with Keystone.
+
+
+
+ {standaloneExamples.map((example) => (
+
+ ))}
+
+
+
+ End-to-End Examples
+
+
+
+ End to end examples demonstrate how a feature works or how to solve a problem with an
+ independent frontend application and a Keystone server.
+
+
+
+ {endToEndExamples.map((example) => (
+
+ ))}
+
+
+
+ Deployment Examples
+
+
+
+ Examples with all the code and documentation you need to get a Keystone project hosted on
+ the web. You can find them in the{' '}
+ Keystone Github Organisation .
+
+
+
+ {deploymentExamples.map((example) => (
+
+ ))}
+
+ >
+ )
+}
diff --git a/docs/app/(site)/docs/examples/page.tsx b/docs/app/(site)/docs/examples/page.tsx
new file mode 100644
index 00000000000..714e9dac62a
--- /dev/null
+++ b/docs/app/(site)/docs/examples/page.tsx
@@ -0,0 +1,44 @@
+import { transform } from '@markdoc/markdoc'
+import { DocsLayout } from '../../../../components/docs/DocsLayout'
+import { reader } from '../../../../keystatic/reader'
+import PageClient from './page-client'
+import { baseMarkdocConfig } from '../../../../markdoc/config'
+
+export const metadata = {
+ title: 'Examples',
+ description: 'A growing collection of projects you can run locally to learn more about Keystone’s many features. Use them as a reference for best practice, and springboard when adding features to your own project.',
+}
+
+export type GroupedExamples = Awaited>
+
+async function getGroupedExamples () {
+ const examples = await reader.collections.examples.all({resolveLinkedFiles: true})
+ const transformedExamples = await Promise.all(examples.map(async example => {
+ return {
+ ...example,
+ entry : {
+ ...example.entry,
+ description: transform(example.entry.description.node, baseMarkdocConfig)
+ }
+ }
+ }))
+
+ const standaloneExamples = transformedExamples.filter(example => example.entry.kind === 'standalone')
+ const endToEndExamples = transformedExamples.filter(example => example.entry.kind === 'end-to-end')
+ const deploymentExamples = transformedExamples.filter(example => example.entry.kind === 'deployment')
+
+ return {
+ standaloneExamples,
+ endToEndExamples,
+ deploymentExamples
+ }
+}
+
+export default async function Docs () {
+ const pageData = await getGroupedExamples()
+ return (
+
+
+
+ )
+}
diff --git a/docs/pages/docs/guides/document-field-demo.tsx b/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx
similarity index 70%
rename from docs/pages/docs/guides/document-field-demo.tsx
rename to docs/app/(site)/docs/guides/document-field-demo/page-client.tsx
index 71d1692b35d..a4069d16453 100644
--- a/docs/pages/docs/guides/document-field-demo.tsx
+++ b/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx
@@ -1,42 +1,22 @@
+
+'use client'
+
import React from 'react'
-import { H1, H2 } from '../../../components/docs/Heading'
-import { DocsPage } from '../../../components/Page'
+
+import { H1, H2 } from '../../../../../components/docs/Heading'
+
import {
DocumentEditorDemo,
DocumentFeaturesProvider,
-} from '../../../components/docs/DocumentEditorDemo'
-import { Well } from '../../../components/primitives/Well'
-import { RelatedContent } from '../../../components/RelatedContent'
-import { InlineCode } from '../../../components/primitives/Code'
+} from '../../../../../components/docs/DocumentEditorDemo'
+import { Well } from '../../../../../components/primitives/Well'
+import { RelatedContent } from '../../../../../components/RelatedContent'
+import { InlineCode } from '../../../../../components/primitives/Code'
export default function DocumentFieldDemo () {
const title = 'Document Fields Demo'
return (
-
+ <>
{title}
@@ -85,6 +65,6 @@ export default function DocumentFieldDemo () {
-
+ >
)
}
diff --git a/docs/app/(site)/docs/guides/document-field-demo/page.tsx b/docs/app/(site)/docs/guides/document-field-demo/page.tsx
new file mode 100644
index 00000000000..953bb958c8d
--- /dev/null
+++ b/docs/app/(site)/docs/guides/document-field-demo/page.tsx
@@ -0,0 +1,36 @@
+import { DocsLayout } from '../../../../../components/docs/DocsLayout'
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Document Fields Demo',
+ description:
+ 'Try Keystone’s powerful new Document field. Experiment with its config settings in real time to shape custom editing experiences.',
+}
+
+export default function DocumentFieldDemo () {
+ return (
+
+
+
+ )
+}
diff --git a/docs/pages/docs/guides/overview.tsx b/docs/app/(site)/docs/guides/overview/page-client.tsx
similarity index 86%
rename from docs/pages/docs/guides/overview.tsx
rename to docs/app/(site)/docs/guides/overview/page-client.tsx
index 0433d4906e0..fff1a218e37 100644
--- a/docs/pages/docs/guides/overview.tsx
+++ b/docs/app/(site)/docs/guides/overview/page-client.tsx
@@ -1,27 +1,17 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
-import { CommunitySlackCTA } from '../../../components/docs/CommunitySlackCTA'
-import { Type } from '../../../components/primitives/Type'
-import { Well } from '../../../components/primitives/Well'
-import { DocsPage } from '../../../components/Page'
-import { useMediaQuery } from '../../../lib/media'
+'use client'
+
+import { CommunitySlackCTA } from '../../../../../components/docs/CommunitySlackCTA'
+import { Type } from '../../../../../components/primitives/Type'
+import { Well } from '../../../../../components/primitives/Well'
+import { useMediaQuery } from '../../../../../lib/media'
export default function Docs () {
const mq = useMediaQuery()
return (
-
+ <>
Keystone Guides
@@ -113,6 +103,6 @@ export default function Docs () {
Learn how to add your own custom pages to Keystone’s Admin UI.
-
+ >
)
}
diff --git a/docs/app/(site)/docs/guides/overview/page.tsx b/docs/app/(site)/docs/guides/overview/page.tsx
new file mode 100644
index 00000000000..a8c53f83910
--- /dev/null
+++ b/docs/app/(site)/docs/guides/overview/page.tsx
@@ -0,0 +1,16 @@
+import { DocsLayout } from '../../../../../components/docs/DocsLayout'
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Guides',
+ description:
+ 'Practical explanations of Keystone’s fundamental building blocks. Learn how to think about, and get the most out of Keystone’s many features.',
+}
+
+export default function Docs () {
+ return (
+
+
+
+ )
+}
diff --git a/docs/app/(site)/docs/page-client.tsx b/docs/app/(site)/docs/page-client.tsx
new file mode 100644
index 00000000000..48ffe26f1a2
--- /dev/null
+++ b/docs/app/(site)/docs/page-client.tsx
@@ -0,0 +1,43 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { CommunitySlackCTA } from '../../../components/docs/CommunitySlackCTA'
+import { Keystone5DocsCTA } from '../../../components/docs/Keystone5DocsCTA'
+import { Type } from '../../../components/primitives/Type'
+import { CommunityCta } from '../../../components/content/CommunityCta'
+import { Alert } from '../../../components/primitives/Alert'
+import { Button } from '../../../components/primitives/Button'
+import { ArrowR } from '../../../components/icons'
+import { KeystoneExperience } from '../../../components/docs/KeystoneExperience'
+
+export default function DocsPageClient ({ featuredExamples, featuredDocs }) {
+
+ return (
+ <>
+
+ Developer Docs
+
+
+
+
+
+
+ Looking for enterprise-grade consulting & support?
+
+
+ Learn more
+
+
+
+ {featuredDocs}
+ {featuredExamples}
+
+ >
+ )
+}
diff --git a/docs/app/(site)/docs/page.tsx b/docs/app/(site)/docs/page.tsx
new file mode 100644
index 00000000000..a81481a282a
--- /dev/null
+++ b/docs/app/(site)/docs/page.tsx
@@ -0,0 +1,21 @@
+import PageClient from './page-client'
+import { FeaturedExamples } from '../../../components/docs/featured-examples'
+import { FeaturedDocs } from '../../../components/docs/featured-docs'
+
+import { DocsLayout } from '../../../components/docs/DocsLayout'
+
+export const metadata = {
+ title: 'Keystone Docs Home',
+ description: 'Developer docs for KeystoneJS: The superpowered headless CMS for developers.',
+}
+
+export default function Docs () {
+ return (
+
+ }
+ featuredDocs={ }
+ />
+
+ )
+}
diff --git a/docs/app/(site)/docs/walkthroughs/page-client.tsx b/docs/app/(site)/docs/walkthroughs/page-client.tsx
new file mode 100644
index 00000000000..170af891063
--- /dev/null
+++ b/docs/app/(site)/docs/walkthroughs/page-client.tsx
@@ -0,0 +1,67 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { Type } from '../../../../components/primitives/Type'
+import { type FeaturedDocsMap } from '../../../../keystatic/get-featured-docs-map'
+import {
+ FeaturedCard,
+ FullWidthCardContainer,
+ SplitCardContainer,
+} from '../../../../components/docs/FeaturedCard'
+
+export default function Docs ({
+ quickstart,
+ walkthroughs,
+}: {
+ quickstart: NonNullable[number]['items'][number]
+ walkthroughs: NonNullable[number]['items']
+}) {
+ return (
+ <>
+
+ Walkthroughs
+
+
+
+ Step-by-step tutorials for building with Keystone.
+
+
+
+ Getting started
+
+
+
+ If you’re new to Keystone begin here. These walkthroughs introduce the system, key concepts,
+ and show you how to get up and running with schema-driven development the Keystone way.
+
+
+
+
+
+
+
+ Learn Keystone
+
+
+
+ Learn how to build a functioning blog backend with relationships, auth, and session data
+ from an empty folder, and gain insights into Keystone’s core concepts along the way.
+
+
+
+ {walkthroughs.map((item) => (
+
+ ))}
+
+ >
+ )
+}
diff --git a/docs/app/(site)/docs/walkthroughs/page.tsx b/docs/app/(site)/docs/walkthroughs/page.tsx
new file mode 100644
index 00000000000..081484041e1
--- /dev/null
+++ b/docs/app/(site)/docs/walkthroughs/page.tsx
@@ -0,0 +1,26 @@
+import { DocsLayout } from '../../../../components/docs/DocsLayout'
+import { getFeaturedDocsMap } from '../../../../keystatic/get-featured-docs-map'
+import { reader } from '../../../../keystatic/reader'
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Walkthroughs',
+ description:
+ 'Explore tutorials with step-by-step instruction on building solutions with Keystone.',
+}
+
+export default async function Docs () {
+ const docs = await getFeaturedDocsMap()
+ if (!docs) throw new Error('No `featuredDocs` found')
+ const featuredDocs = docs[0]
+ const [quickstart, ...walkthroughs] = featuredDocs.items
+
+ return (
+
+
+
+ )
+}
diff --git a/docs/pages/ds.tsx b/docs/app/(site)/ds/page-client.tsx
similarity index 94%
rename from docs/pages/ds.tsx
rename to docs/app/(site)/ds/page-client.tsx
index e631e7c4e7e..41ddd4e74a6 100644
--- a/docs/pages/ds.tsx
+++ b/docs/app/(site)/ds/page-client.tsx
@@ -1,24 +1,25 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import { Fragment, useState } from 'react'
-import { jsx } from '@emotion/react'
-import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../components/content/CodeWindow'
-import { GitHubButton } from '../components/primitives/GitHubButton'
-import { COLORS, TYPESCALE, TYPE, SPACE } from '../lib/TOKENS'
-import { Highlight } from '../components/primitives/Highlight'
-import { styleMap, Type } from '../components/primitives/Type'
-import { InlineCode } from '../components/primitives/Code'
-import { Status } from '../components/primitives/Status'
-import { Button } from '../components/primitives/Button'
-import { Alert } from '../components/primitives/Alert'
-import { Badge } from '../components/primitives/Badge'
-import { Emoji } from '../components/primitives/Emoji'
-import { Stack } from '../components/primitives/Stack'
-import { Field } from '../components/primitives/Field'
-import { Well } from '../components/primitives/Well'
-import * as allIcons from '../components/icons'
-import { Page } from '../components/Page'
+import { CodeWindow, WindowWrapper, WindowL, WindowR } from '../../../components/content/CodeWindow'
+import { GitHubButton } from '../../../components/primitives/GitHubButton'
+import { COLORS, TYPESCALE, TYPE, SPACE } from '../../../lib/TOKENS'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { styleMap, Type } from '../../../components/primitives/Type'
+import { InlineCode } from '../../../components/primitives/Code'
+import { Status } from '../../../components/primitives/Status'
+import { Button } from '../../../components/primitives/Button'
+import { Alert } from '../../../components/primitives/Alert'
+import { Badge } from '../../../components/primitives/Badge'
+import { Emoji } from '../../../components/primitives/Emoji'
+import { Stack } from '../../../components/primitives/Stack'
+import { Field } from '../../../components/primitives/Field'
+import { Well } from '../../../components/primitives/Well'
+import * as allIcons from '../../../components/icons'
+import { Page } from '../../../components/Page'
const EXCEPT_ICONS = ['FrontEndLogos', 'ClientLogos']
@@ -96,10 +97,7 @@ export default function DS () {
let firstGrad: string
return (
-
+
Design System
@@ -264,7 +262,7 @@ export default function DS () {
Type
- {(Object.keys(styleMap) as Array).map(style => (
+ {(Object.keys(styleMap) as Array).map((style) => (
Type {style}
diff --git a/docs/app/(site)/ds/page.tsx b/docs/app/(site)/ds/page.tsx
new file mode 100644
index 00000000000..5991a0df853
--- /dev/null
+++ b/docs/app/(site)/ds/page.tsx
@@ -0,0 +1,10 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Design System Components and Tokens',
+ description: 'Design System Components & Tokens for the KeystoneJS website',
+}
+
+export default function DS () {
+ return
+}
diff --git a/docs/pages/enterprise.tsx b/docs/app/(site)/enterprise/page-client.tsx
similarity index 76%
rename from docs/pages/enterprise.tsx
rename to docs/app/(site)/enterprise/page-client.tsx
index 9b29ae31bb3..5e851cf3547 100644
--- a/docs/pages/enterprise.tsx
+++ b/docs/app/(site)/enterprise/page-client.tsx
@@ -1,24 +1,25 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-import { useMediaQuery } from '../lib/media'
-import { Highlight } from '../components/primitives/Highlight'
-import { MWrapper } from '../components/content/MWrapper'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { Pill } from '../components/content/Pill'
-import { Page } from '../components/Page'
-import { ContactForm } from '../components/ContactForm'
-import { CustomerCard } from '../components/content/CustomerCard'
-import { VocalLogo } from '../components/icons/VocalLogo'
-import { PJohnsonLogo } from '../components/icons/PJohnsonLogo'
-import { DFATLogo } from '../components/icons/DFATLogo'
-import { EnliticLogo } from '../components/icons/EnliticLogo'
-import { RugbyAuLogo } from '../components/icons/RugbyAuLogo'
-import { WestpacLogo } from '../components/icons/WestpacLogo'
-import { PrintBarLogo } from '../components/icons/PrintBarLogo'
-import { IntroHeading, IntroLead, IntroWrapper } from '../components/content/Intro'
-import { Stack } from '../components/primitives/Stack'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { useMediaQuery } from '../../../lib/media'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { Pill } from '../../../components/content/Pill'
+import { Page } from '../../../components/Page'
+import { ContactForm } from '../../../components/ContactForm'
+import { CustomerCard } from '../../../components/content/CustomerCard'
+import { VocalLogo } from '../../../components/icons/VocalLogo'
+import { PJohnsonLogo } from '../../../components/icons/PJohnsonLogo'
+import { DFATLogo } from '../../../components/icons/DFATLogo'
+import { EnliticLogo } from '../../../components/icons/EnliticLogo'
+import { RugbyAuLogo } from '../../../components/icons/RugbyAuLogo'
+import { WestpacLogo } from '../../../components/icons/WestpacLogo'
+import { PrintBarLogo } from '../../../components/icons/PrintBarLogo'
+import { IntroHeading, IntroLead, IntroWrapper } from '../../../components/content/Intro'
+import { Stack } from '../../../components/primitives/Stack'
const customers = [
{
@@ -64,14 +65,8 @@ const customers = [
export default function ForOrganisations () {
const mq = useMediaQuery()
-
return (
-
+
Keystone for Enterprise
@@ -134,15 +129,15 @@ export default function ForOrganisations () {
{learnMoreHref ? (
-
+
{copy}
-
-
+
+
) : (
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',
})}
>
-
+
- 160+
+ 250+
Contributors
diff --git a/docs/app/(site)/page.tsx b/docs/app/(site)/page.tsx
new file mode 100644
index 00000000000..4e3f0aa8305
--- /dev/null
+++ b/docs/app/(site)/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'KeystoneJS: The superpowered Node.js Headless CMS for developers',
+ description:
+ 'Build faster and scale further with the programmable open source GraphQL API back-end for structured content projects.',
+}
+
+export default function Page () {
+ return
+}
diff --git a/docs/app/(site)/roadmap/page-client.tsx b/docs/app/(site)/roadmap/page-client.tsx
new file mode 100644
index 00000000000..ee0d9ba20ea
--- /dev/null
+++ b/docs/app/(site)/roadmap/page-client.tsx
@@ -0,0 +1,528 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+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 { Type } from '../../../components/primitives/Type'
+import { Page } from '../../../components/Page'
+import { EndCta } from '../../../components/content/EndCta'
+import { Alert } from '../../../components/primitives/Alert'
+import { Emoji } from '../../../components/primitives/Emoji'
+import { Fragment, type ComponentProps, type ReactNode } from 'react'
+import { Gradient } from '../../../components/primitives/Gradient'
+import { InlineCode } from '../../../components/primitives/Code'
+import { Tick } from '../../../components/icons'
+
+function TimelineItem ({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function TimelineMarker ({ look }: Pick, 'look'>) {
+ return (
+
+ )
+}
+
+function TimelineWeAreHere () {
+ const arrowSize = '0.4rem'
+ const mq = useMediaQuery()
+ return (
+
+
+ We are here!
+
+ )
+}
+
+type TimelineContentProps = {
+ title: ReactNode
+ children: ReactNode
+} & Pick, 'look'>
+
+function TimelineContent ({ title, look, children }: TimelineContentProps) {
+ return (
+
+
+ {title}
+
+
+ {children}
+
+
+ )
+}
+
+type RoadmapListProps = {
+ children: ReactNode
+}
+
+function RoadmapList ({ children }: RoadmapListProps) {
+ const mq = useMediaQuery()
+ return (
+
+ )
+}
+
+const roadmapItemSectionStyles = {
+ margin: '0rem 0 .75rem',
+ borderRadius: '.4rem',
+ display: 'inline-block',
+ padding: '0.1825rem 0.5rem',
+ fontSize: '0.9rem',
+ fontWeight: 700,
+}
+
+const roadmapItemSection = {
+ docs: () => (
+ Docs
+ ),
+ 'fields and schema': () => (
+
+ Fields & Schema
+
+ ),
+ core: () => (
+ Core
+ ),
+ 'admin ui': () => (
+
+ Admin UI
+
+ ),
+}
+
+type RoadmapItemProps = {
+ title: ReactNode
+ section?: keyof typeof roadmapItemSection
+ isReleased?: boolean
+ children: ReactNode
+}
+
+function RoadmapItem ({ title, section, isReleased = false, children }: RoadmapItemProps) {
+ const Section = section ? roadmapItemSection[section] : null
+ return (
+
+ {isReleased && (
+
+ )}
+ {Section && }
+
+ {title}
+
+
+ {children}
+
+
+ )
+}
+
+function Divider () {
+ return (
+
+ )
+}
+
+export default function Roadmap () {
+ const mq = useMediaQuery()
+
+ return (
+
+
+
+
+ The Keystone Roadmap
+
+
+ Keystone 6 is actively maintained and steadily improving. We've graduated to the{' '}
+ @keystone-6 namespace on npm and have a stable set of APIs that
+ you can confidently build on 🚀
+
+
+
+
+ To see what we've recently shipped, checkout our release notes
+
+
+
+
+
+
+
+ Surveyed the landscape. Formed concepts around what a next-gen experience would look
+ like. Started laying the foundations.
+
+
+
+
+
+ Published the New Interfaces as{' '}
+ @keystone-next
+ on npm. Commenced iteration based on community & internal feedback.
+
+
+
+
+
+
+ Stabilised the new architecture & APIs. Docs & example projects. Published as{' '}
+ @keystone-6 on npm.
+
+
+
+
+
+ A better dev-configured editing experience. Maturity & features in Keystone core. More
+ pathways to grow with Keystone.
+
+
+
+
+ What's up next
+
+
+ We're using the foundations we shipped in 2021 as a springboard to bring{' '}
+ developers,{' '}
+ project owners, and{' '}
+ editors into more productive ways of working
+ together. Our key areas of focus are:
+
+
+
+
+ Schema-driven auth that streamlines how users authenticate with
+ Keystone applications
+
+
+ A next-gen Admin UI that unifies developer and editor collaborations
+ in new and exciting ways
+
+
+ Maturing the DX with better Types and capabilities for self-hosting
+ and media management
+
+
+
+
+ Schema-driven authentication
+
+
+ Keystone operates on the schema-driven principle. Right now, authentication in Keystone is
+ left up to the developers to implement. This leads to every Keystone project having auth
+ handled in a slightly different way.
+
+
+ We want to change that and offer a predictable, schema-driven first-party authentication
+ solution for Keystone.
+
+
+ Next-gen Admin UI
+
+
+ Our design and labs teams have re-imagined a state-of-the-art Admin UI. One that is
+ responsive, accessible and functional. The fruit of this work led to{' '}
+ Keystar UI ,
+ an internal design system we've been battle-testing with{' '}
+ Keystatic .
+
+
+
+ This body of work will elevate the experience of authoring content in Keystone to the same
+ high standards we have for authoring with Keystone's core APIs.
+
+
+ Maturing the Developer Experience
+
+
+ At{' '}
+
+ Thinkmill
+
+ , we have a longstanding commitment to open source that includes the likes of{' '}
+
+ react select
+
+ ,{' '}
+
+ classnames
+
+ ,{' '}
+
+ Keystatic
+
+ , and of course Keystone.
+
+
+ We'll continue to iterate on this knowledge and learning loop to make Keystone the easiest
+ way to design and standup a GraphQL API on the web. Going all-in on Typescript has made
+ the DX so sweet, but we want to take it further so that Keystone's the best
+ Typescript > GraphQL developer experience in the ecosystem.{' '}
+
+
+
+ We'll also focus on making better pathways for you to integrate Keystone with the
+ deployment services you use most, so you can get the most out of where the modern web is
+ going. Image and file management is an area we're actively iterating on, and we'll have
+ more to share soon.
+
+
+
+ Feature roadmap
+
+
+
+ Here's what we've been working on, and what's coming next.
+
+
+
+ Recently released
+
+
+
+
+ A way to define a single object in schema that's editable in Admin UI and accessible
+ in the GraphQL API. Handy for storing website & social settings, API keys, and{' '}
+ more.
+
+
+
+
+ It's often easier to work with content when the form is grouped into different
+ sections of related fields. Learn more
+
+
+
+
+ Access your GraphQL APIs from Node.js for greater flexibility when writing apps and
+ hybrid use-cases.
+
+
+
+
+
+
+ Current focus
+
+
+
+
+ Dynamically showing fields based on the value of other fields is a great way to
+ improve editing flow and content integrity.
+
+
+
+
+ Make the Admin UI responsive, accessible and i18n compliant with the battle-tested{' '}
+
+ Keystar UI
+ {' '}
+ design system.
+
+
+
+
+
+ Next up
+
+
+
+
+ Sometimes you need to manage data in structures that are nested and/or repeating.
+ We're working on a way to define these in schema and have them stored as JSON field in
+ the database.
+
+
+
+
+ Certain list types come with a need to order the items they contain. We're looking in
+ to an approach that's easy to implement in schema.
+
+
+
+
+ We're broadening our list of streamlined scenarios & looking into options for
+ serverless environments.
+
+
+
+
+
+ Further afield
+
+
+
+
+ Built-in tooling for you to give editors a sense of how their content will be consumed
+ by end users.
+
+
+
+
+ Design your own journey from draft to{' '}
+ published to meet the unique needs of your content team.
+
+
+
+
+ More powerful interfaces for managing different scales of related data, from small to
+ really really large
+
+
+
+
+ Long lasting server operations that can change their result over time. Handy for
+ updating your front-end in real time when important data changes in your backend.
+
+
+
+
+ A built-in schema-driven approach to supporting multilingual content projects.
+
+
+
+
+ If you want to update an item but aren't sure if it exists, this will update the item
+ if it's there or create a new item with the data you've provided.
+
+
+
+
+ The future of deployment is serverless, and we're tracking the state of the ecosystem
+ to make sure Keystone is ready for it.
+
+
+
+
+ A way for you and editors to easily search for strings across your entire dataset.
+ Handy for when you need something specific but don't know where it lives.
+
+
+
+
+
+ Got a feature you're after, or want to know more about the future?
+
+
+ Join the Keystone conversation on{' '}
+
+ Slack
+
+ ,{' '}
+
+ GitHub
+
+ , and{' '}
+
+ Twitter
+
+ .
+
+
+
+
+
+ )
+}
diff --git a/docs/app/(site)/roadmap/page.tsx b/docs/app/(site)/roadmap/page.tsx
new file mode 100644
index 00000000000..94a54e89c71
--- /dev/null
+++ b/docs/app/(site)/roadmap/page.tsx
@@ -0,0 +1,10 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Roadmap',
+ description: "Discover where KeystoneJS is headed, and why we're going there.",
+}
+
+export default function ForOrganisations () {
+ return
+}
diff --git a/docs/pages/why-keystone.tsx b/docs/app/(site)/why-keystone/page-client.tsx
similarity index 91%
rename from docs/pages/why-keystone.tsx
rename to docs/app/(site)/why-keystone/page-client.tsx
index 11eee71e283..9ec0383d22d 100644
--- a/docs/pages/why-keystone.tsx
+++ b/docs/app/(site)/why-keystone/page-client.tsx
@@ -1,52 +1,48 @@
-/** @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 { Highlight } from '../components/primitives/Highlight'
-import { Relationship } from '../components/icons/Relationship'
-import { WhyKeystone } from '../components/icons/WhyKeystone'
-import { MWrapper } from '../components/content/MWrapper'
-import { TweetBox } from '../components/content/TweetBox'
-import { Typescript } from '../components/icons/Typescript'
-import { Automated } from '../components/icons/Automated'
-import { Migration } from '../components/icons/Migration'
-import { Section } from '../components/content/Section'
-import { Button } from '../components/primitives/Button'
-import { AdvancedReactCta } from '../components/content/AdvancedReactCta'
-import { EndCta } from '../components/content/EndCta'
-import { Emoji } from '../components/primitives/Emoji'
-import { Updates } from '../components/icons/Updates'
-import { Quote } from '../components/content/Quote'
-import { Type } from '../components/primitives/Type'
-import { ArrowR } from '../components/icons/ArrowR'
-import { Code } from '../components/icons/Code'
-import { Content } from '../components/icons/Content'
-import { Organization } from '../components/icons/Organization'
-import { Custom } from '../components/icons/Custom'
-import { Editor } from '../components/icons/Editor'
-import { Filter } from '../components/icons/Filter'
-import { Pill } from '../components/content/Pill'
-import { Shield } from '../components/icons/Shield'
-import { Watch } from '../components/icons/Watch'
-import { Cli } from '../components/icons/Cli'
-import { Page } from '../components/Page'
-import { IntroHeading } from '../components/content/Intro'
+import { useMediaQuery } from '../../../lib/media'
+import { Highlight } from '../../../components/primitives/Highlight'
+import { Relationship } from '../../../components/icons/Relationship'
+import { WhyKeystone } from '../../../components/icons/WhyKeystone'
+import { MWrapper } from '../../../components/content/MWrapper'
+import { TweetBox } from '../../../components/content/TweetBox'
+import { Typescript } from '../../../components/icons/Typescript'
+import { Automated } from '../../../components/icons/Automated'
+import { Migration } from '../../../components/icons/Migration'
+import { Section } from '../../../components/content/Section'
+import { Button } from '../../../components/primitives/Button'
+import { AdvancedReactCta } from '../../../components/content/AdvancedReactCta'
+import { EndCta } from '../../../components/content/EndCta'
+import { Emoji } from '../../../components/primitives/Emoji'
+import { Updates } from '../../../components/icons/Updates'
+import { Quote } from '../../../components/content/Quote'
+import { Type } from '../../../components/primitives/Type'
+import { ArrowR } from '../../../components/icons/ArrowR'
+import { Code } from '../../../components/icons/Code'
+import { Content } from '../../../components/icons/Content'
+import { Organization } from '../../../components/icons/Organization'
+import { Custom } from '../../../components/icons/Custom'
+import { Editor } from '../../../components/icons/Editor'
+import { Filter } from '../../../components/icons/Filter'
+import { Pill } from '../../../components/content/Pill'
+import { Shield } from '../../../components/icons/Shield'
+import { Watch } from '../../../components/icons/Watch'
+import { Cli } from '../../../components/icons/Cli'
+import { Page } from '../../../components/Page'
+import { IntroHeading } from '../../../components/content/Intro'
-import adminUi from '../public/assets/admin-ui.png'
+import adminUi from '../../../public/assets/admin-ui.png'
export default function WhyKeystonePage () {
const mq = useMediaQuery()
return (
-
+
Why Keystone
diff --git a/docs/app/(site)/why-keystone/page.tsx b/docs/app/(site)/why-keystone/page.tsx
new file mode 100644
index 00000000000..ff1723d09cc
--- /dev/null
+++ b/docs/app/(site)/why-keystone/page.tsx
@@ -0,0 +1,11 @@
+import PageClient from './page-client'
+
+export const metadata = {
+ title: 'Why KeystoneJS',
+ description:
+ 'More than a backend framework, and more than a Headless CMS, discover why Keystone is the platform for next-gen development workflows and evolution.',
+}
+
+export default function WhyKeystonePage () {
+ return
+}
diff --git a/docs/pages/api/hero-image.tsx b/docs/app/api/hero-image/route.tsx
similarity index 84%
rename from docs/pages/api/hero-image.tsx
rename to docs/app/api/hero-image/route.tsx
index 50a923675c2..81faba252ee 100644
--- a/docs/pages/api/hero-image.tsx
+++ b/docs/app/api/hero-image/route.tsx
@@ -1,15 +1,10 @@
-import React from 'react'
import { ImageResponse } from '@vercel/og'
-import type { NextRequest } from 'next/server'
-import { siteBaseUrl } from '../../lib/og-util'
-
-export const config = {
- runtime: 'experimental-edge',
-}
+import { type NextRequest } from 'next/server'
+import { siteBaseUrl } from '../../../lib/og-util'
const bgImgUrl = `url(${siteBaseUrl}/assets/blog/blog-cover-bg.png)`
-export const HeroImage = ({ title, type }: { title: string, type?: string }) => {
+const HeroImage = ({ title, type }: { title: string, type?: string }) => {
const clippedTitle = title.length > 100 ? title.substring(0, 100) + '...' : title
let titleFontSize = 96
if (clippedTitle.length > 35) {
@@ -26,7 +21,6 @@ export const HeroImage = ({ title, type }: { title: string, type?: string }) =>
style={{
display: 'flex',
backgroundColor: 'white',
- // backgroundImage: 'linear-gradient(115.92deg, #1476FF 22.53%, #00ABDA 88.23%)',
backgroundImage: bgImgUrl,
height: '100%',
width: '100%',
@@ -107,14 +101,13 @@ export const HeroImage = ({ title, type }: { title: string, type?: string }) =>
}
const interSemiBold = fetch(
- new URL('../../public/assets/blog/font/Inter-SemiBold.ttf', import.meta.url)
-).then(res => res.arrayBuffer())
+ new URL('../../../public/assets/blog/font/Inter-SemiBold.ttf', import.meta.url)
+).then((res) => res.arrayBuffer())
const interExtraBold = fetch(
- new URL('../../public/assets/blog/font/Inter-ExtraBold.ttf', import.meta.url)
-).then(res => res.arrayBuffer())
+ new URL('../../../public/assets/blog/font/Inter-ExtraBold.ttf', import.meta.url)
+).then((res) => res.arrayBuffer())
-// vercel API route that generates the OG image
-export default async function handler (req: NextRequest) {
+export async function GET (req: NextRequest) {
const interSemiBoldData = await interSemiBold
const interExtraBoldData = await interExtraBold
@@ -158,3 +151,5 @@ export default async function handler (req: NextRequest) {
})
}
}
+
+export const runtime = 'edge'
diff --git a/docs/components/Announce.tsx b/docs/components/Announce.tsx
index 9a094aa3802..7e6c59326a3 100644
--- a/docs/components/Announce.tsx
+++ b/docs/components/Announce.tsx
@@ -1,6 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes, type ReactNode } from 'react'
import { Wrapper } from './primitives/Wrapper'
diff --git a/docs/components/Breadcrumbs.tsx b/docs/components/Breadcrumbs.tsx
index b55d64aa8a5..929611c12ce 100644
--- a/docs/components/Breadcrumbs.tsx
+++ b/docs/components/Breadcrumbs.tsx
@@ -1,7 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { useRouter } from 'next/router'
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { usePathname, useRouter } from 'next/navigation'
import Link from 'next/link'
import { Type } from './primitives/Type'
@@ -10,9 +11,10 @@ type Path = { title: string, href: string }
export function Breadcrumbs () {
const router = useRouter()
+ const pathname = usePathname()
// remove anchor and split path
- const linkPath = new URL(router.asPath, 'https://keystonejs.com').pathname.split('/')
+ const linkPath = new URL(pathname || '', 'https://keystonejs.com').pathname.split('/')
linkPath.shift()
const breadcrumbs = linkPath.map((path, i): Path => {
diff --git a/docs/components/ContactForm.tsx b/docs/components/ContactForm.tsx
index e7f940568e9..b03c410d563 100644
--- a/docs/components/ContactForm.tsx
+++ b/docs/components/ContactForm.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import { Fragment, useState, type ReactNode, type SyntheticEvent, type HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import { useMediaQuery } from '../lib/media'
import { Button } from './primitives/Button'
diff --git a/docs/components/Footer.tsx b/docs/components/Footer.tsx
index 516e821f3e8..53d380a70fc 100644
--- a/docs/components/Footer.tsx
+++ b/docs/components/Footer.tsx
@@ -1,6 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
+
import Link from 'next/link'
import { type HTMLAttributes } from 'react'
@@ -142,10 +142,10 @@ export function Footer () {
- Latest News
+ Blog
- Roadmap
+ Roadmap
Release Notes
diff --git a/docs/components/Header.tsx b/docs/components/Header.tsx
index 46ae5ca9f45..e109db54e5d 100644
--- a/docs/components/Header.tsx
+++ b/docs/components/Header.tsx
@@ -1,5 +1,7 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import {
createContext,
useContext,
@@ -10,8 +12,7 @@ import {
type ReactNode,
type RefObject,
} from 'react'
-import { useRouter } from 'next/router'
-import { jsx } from '@emotion/react'
+import { useRouter, usePathname } from 'next/navigation'
import Link from 'next/link'
import debounce from 'lodash.debounce'
@@ -75,8 +76,8 @@ function Logo () {
}
function useCurrentSection () {
- const { pathname } = useRouter()
- const check = (candidate: string) => pathname.startsWith(candidate)
+ const pathname = usePathname()
+ const check = (candidate: string) => pathname?.startsWith(candidate)
if (['/updates', '/releases'].some(check)) return '/updates'
if (['/why-keystone', '/for-'].some(check)) return '/why-keystone'
if (['/docs'].some(check)) return '/docs'
@@ -133,7 +134,7 @@ function FlatMenu ({
const [showContent, setShowContent] = useState(false)
const onClickHandler = useCallback(() => {
- setShowContent(b => !b)
+ setShowContent((b) => !b)
}, [setShowContent])
const closeMenu = useCallback(() => {
@@ -196,7 +197,7 @@ function FlatMenu ({
border: '1px solid var(--border)',
borderRadius: '0.25rem',
background: 'var(--app-bg)',
- gap: '1.25rem',
+ gap: '.25rem',
}}
>
{items.map(({ href, label }) => {
@@ -208,7 +209,7 @@ function FlatMenu ({
alwaysVisible
href={href}
css={{
- padding: '0 !important',
+ padding: '0.5rem 0 !important',
}}
>
{label}
@@ -224,6 +225,7 @@ function FlatMenu ({
export function Header () {
const mq = useMediaQuery()
const router = useRouter()
+ const pathname = usePathname()
const menuRef = useRef(null)
const headerRef = useRef(null)
@@ -295,7 +297,7 @@ export function Header () {
loadSearch(searchAttempt)
// search - keyboard shortcut
let keysPressed: { [key: KeyboardEvent['key']]: boolean } = {}
- document.body.addEventListener('keydown', event => {
+ document.body.addEventListener('keydown', (event) => {
// If we're typing in an input, don't ever focus the search input
if (
document.activeElement &&
@@ -310,7 +312,7 @@ export function Header () {
document.getElementById('search-field')?.focus()
}
})
- document.body.addEventListener('keyup', event => {
+ document.body.addEventListener('keyup', (event) => {
delete keysPressed[event.key]
})
}, [])
@@ -328,11 +330,8 @@ export function Header () {
}, [])
useEffect(() => {
- router.events.on('routeChangeComplete', handleClose)
- return () => {
- router.events.off('routeChangeComplete', handleClose)
- }
- }, [router.events, handleClose])
+ handleClose()
+ }, [pathname])
return (
@@ -386,7 +385,7 @@ export function Header () {
{ label: 'For Developers', href: '/for-developers' },
{ label: 'For Organisations', href: '/for-organisations' },
{ label: 'For Content Management', href: '/for-content-management' },
- { label: 'Our Roadmap', href: '/updates/roadmap' },
+ { label: 'Our Roadmap', href: '/roadmap' },
{ label: 'GitHub Releases', href: 'https://github.com/keystonejs/keystone/releases' },
{ label: 'Enterprise', href: '/enterprise' },
]}
diff --git a/docs/components/Markdoc.tsx b/docs/components/Markdoc.tsx
index 47b2ffdc031..62e5841f5c1 100644
--- a/docs/components/Markdoc.tsx
+++ b/docs/components/Markdoc.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { type ElementType, type ReactNode } from 'react'
import type { RenderableTreeNodes, Scalar, RenderableTreeNode, Tag } from '@markdoc/markdoc'
import { isTag } from '../markdoc/isTag'
@@ -12,7 +14,7 @@ import { ComingSoon } from './docs/ComingSoon'
const renderers: Record = {
code: InlineCode,
CodeBlock (props: { content: string, language: string }) {
- return (
+ return (
{props.content}
@@ -82,37 +84,4 @@ export function Markdoc (props: { content: RenderableTreeNodes }) {
}
return render(props.content) as JSX.Element
-}
-
-export type HeadingType = {
- id: string
- depth: number
- label: string
-}
-
-export function extractHeadings (content: Tag): HeadingType[] {
- const headings: HeadingType[] = []
- for (const child of content.children) {
- if (isTag(child) && child.name === 'Heading') {
- headings.push({
- id: child.attributes.id,
- depth: child.attributes.level,
- label: stringifyDocContent(child),
- })
- }
- }
- return headings
-}
-
-function stringifyDocContent (node: RenderableTreeNode): string {
- if (typeof node === 'string') {
- return node
- }
- if (Array.isArray(node)) {
- return node.map(stringifyDocContent).join('')
- }
- if (!isTag(node)) {
- return ''
- }
- return node.children.map(stringifyDocContent).join('')
-}
+}
\ No newline at end of file
diff --git a/docs/components/MobileMenu.tsx b/docs/components/MobileMenu.tsx
index be2ac9fb1bf..521e1bcb15d 100644
--- a/docs/components/MobileMenu.tsx
+++ b/docs/components/MobileMenu.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import Link from 'next/link'
// import { useRouter } from 'next/router';
import { Fragment, useEffect, type ReactNode, type MouseEvent } from 'react'
@@ -49,6 +48,7 @@ export function MobileMenu ({ handleClose }: MobileMenuProps) {
For Developers
For Organisations
For Content Management
+ Roadmap
For Enterprise
- {/*
Updates */}
Blog
-
Roadmap
GitHub Releases
diff --git a/docs/components/Page.tsx b/docs/components/Page.tsx
index 8ebd3f0db36..ec2863db525 100644
--- a/docs/components/Page.tsx
+++ b/docs/components/Page.tsx
@@ -1,258 +1,106 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import { useRef, Fragment, type ReactNode } from 'react'
-import { useRouter } from 'next/router'
-import { jsx } from '@emotion/react'
-import Head from 'next/head'
+import { usePathname } from 'next/navigation'
import { useMediaQuery } from '../lib/media'
import { TableOfContents } from './docs/TableOfContents'
import { Wrapper } from './primitives/Wrapper'
import { EditButton } from './primitives/EditButton'
import { Breadcrumbs } from './Breadcrumbs'
-import { Sidebar } from './docs/Sidebar'
import { Stack } from './primitives/Stack'
import { Header } from './Header'
-import { Footer, DocsFooter } from './Footer'
+import { Footer } from './Footer'
import { type HeadingType } from './Markdoc'
-function OpenGraph ({
- title,
- description,
- ogImage,
-}: {
- title: string
- description: string
- ogImage?: string
-}) {
- const siteUrl = process.env.siteUrl
- if (!ogImage) {
- ogImage = `${siteUrl}/og-image-landscape.png`
- }
- return (
-
-
{title}
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-const pagesWithUpdatesSidebar = ['/updates']
-export function DocsPage ({
- children,
- headings = [],
- noProse,
- noRightNav,
- title,
- description,
- ogImage,
- isIndexPage,
- editPath,
-}: {
- children: ReactNode
- headings?: HeadingType[]
- noProse?: boolean
- noRightNav?: boolean
- title: string
- description: string
- ogImage?: string
- isIndexPage?: boolean
- editPath?: string
-}) {
- const contentRef = useRef
(null)
- const mq = useMediaQuery()
- const { pathname } = useRouter()
- const isUpdatesPage = pagesWithUpdatesSidebar.some(p => pathname.startsWith(p))
-
- const metaTitle = title ? `${title} - Keystone 6 Documentation` : `Keystone 6 Documentation`
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
- {!!headings.length && !noRightNav && (
-
- )}
-
-
-
-
-
- )
-}
-
export function BlogPage ({
children,
headings = [],
noRightNav,
- title,
- description,
- ogImage,
isIndexPage,
editPath,
}: {
children: ReactNode
headings?: HeadingType[]
noRightNav?: boolean
- title: string
- description: string
- ogImage?: string
isIndexPage?: boolean
editPath?: string
}) {
const contentRef = useRef(null)
const mq = useMediaQuery()
- const { pathname } = useRouter()
-
- const metaTitle = title ? `${title} | Keystone Blog` : `Keystone Blog`
+ const pathname = usePathname()
return (
-
-
-
+
+
-
-
-
-
-
-
+
-
-
-
- {children}
-
-
- {!!headings.length && !noRightNav && (
-
- )}
-
-
-
-
-
+
+
+
+ {children}
+
+
+ {!!headings.length && !noRightNav && (
+
+ )}
+
+
+
+
)
}
-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 (
{
+ theme.setTheme(theme.theme === 'dark' ? 'light' : 'dark')
+ }}
css={{
display: 'inline-flex',
appearance: 'none',
@@ -64,7 +41,18 @@ export function ThemeToggle (props: HTMLAttributes) {
}}
{...props}
>
- {theme === null ? : }
+
+
)
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) {
-
+
Get started
diff --git a/docs/components/content/Intro.tsx b/docs/components/content/Intro.tsx
index 8eb7077ef8b..66aca6560fe 100644
--- a/docs/components/content/Intro.tsx
+++ b/docs/components/content/Intro.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'
diff --git a/docs/components/content/MWrapper.tsx b/docs/components/content/MWrapper.tsx
index 16fbb3c07d6..1ec02e0f6ac 100644
--- a/docs/components/content/MWrapper.tsx
+++ b/docs/components/content/MWrapper.tsx
@@ -1,7 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import type { ElementType, HTMLAttributes } from 'react'
-import { jsx } from '@emotion/react'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/content/Pill.tsx b/docs/components/content/Pill.tsx
index 9a9105a812d..86b9ab8db23 100644
--- a/docs/components/content/Pill.tsx
+++ b/docs/components/content/Pill.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
type PillProps = {
diff --git a/docs/components/content/PillCta.tsx b/docs/components/content/PillCta.tsx
index 36518b8fcec..cbc91730e62 100644
--- a/docs/components/content/PillCta.tsx
+++ b/docs/components/content/PillCta.tsx
@@ -1,6 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/content/Quote.tsx b/docs/components/content/Quote.tsx
index 764dd26ec10..632ca40026a 100644
--- a/docs/components/content/Quote.tsx
+++ b/docs/components/content/Quote.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/content/Section.tsx b/docs/components/content/Section.tsx
index 669ab8a838e..189b9215806 100644
--- a/docs/components/content/Section.tsx
+++ b/docs/components/content/Section.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/TweetBox.tsx b/docs/components/content/TweetBox.tsx
index 0205cb6cca0..8f62b23e444 100644
--- a/docs/components/content/TweetBox.tsx
+++ b/docs/components/content/TweetBox.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 { Quote } from '../icons/Quote'
diff --git a/docs/components/docs/ComingSoon.tsx b/docs/components/docs/ComingSoon.tsx
index 04e71539d17..cf341be0b30 100644
--- a/docs/components/docs/ComingSoon.tsx
+++ b/docs/components/docs/ComingSoon.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Alert } from '../primitives/Alert'
diff --git a/docs/components/docs/CommunitySlackCTA.tsx b/docs/components/docs/CommunitySlackCTA.tsx
index 8534baf3674..9ee4115c2ee 100644
--- a/docs/components/docs/CommunitySlackCTA.tsx
+++ b/docs/components/docs/CommunitySlackCTA.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
import { Alert } from '../primitives/Alert'
import { Button } from '../primitives/Button'
diff --git a/docs/components/docs/CopyToClipboard.tsx b/docs/components/docs/CopyToClipboard.tsx
index ccd49113b63..bb9782a8524 100644
--- a/docs/components/docs/CopyToClipboard.tsx
+++ b/docs/components/docs/CopyToClipboard.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Link } from '../icons/Link'
diff --git a/docs/components/docs/DocsFooter.tsx b/docs/components/docs/DocsFooter.tsx
deleted file mode 100644
index 219f598b4cd..00000000000
--- a/docs/components/docs/DocsFooter.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
-import { Wrapper } from '../primitives/Wrapper'
-import { Emoji } from '../primitives/Emoji'
-
-export function DocsFooter () {
- return (
-
-
- Made in by Thinkmill. Supported with{' '}
- by the awesome Keystone community.
-
-
- )
-}
diff --git a/docs/components/docs/DocsLayout.tsx b/docs/components/docs/DocsLayout.tsx
new file mode 100644
index 00000000000..127d57a6720
--- /dev/null
+++ b/docs/components/docs/DocsLayout.tsx
@@ -0,0 +1,6 @@
+import { DocsLayoutClient } from './DocsLayoutClient'
+import { DocsNavigation } from './docs-navigation'
+
+export async function DocsLayout (props) {
+ return } />
+}
diff --git a/docs/components/docs/DocsLayoutClient.tsx b/docs/components/docs/DocsLayoutClient.tsx
new file mode 100644
index 00000000000..e05c4a5d7ea
--- /dev/null
+++ b/docs/components/docs/DocsLayoutClient.tsx
@@ -0,0 +1,104 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { type ReactNode, useRef } from 'react'
+import { usePathname } from 'next/navigation'
+import { Header } from '../Header'
+import { Wrapper } from '../primitives/Wrapper'
+import { Sidebar } from './Sidebar'
+import { Stack } from '../primitives/Stack'
+import { Breadcrumbs } from '../Breadcrumbs'
+import { EditButton } from '../primitives/EditButton'
+import { TableOfContents } from './TableOfContents'
+
+import { useMediaQuery } from '../../lib/media'
+import { DocsFooter } from '../Footer'
+import { type HeadingType } from '../../markdoc/headings'
+
+export function DocsLayoutClient ({
+ children,
+ headings = [],
+ noProse,
+ noRightNav,
+ isIndexPage,
+ editPath,
+ docsNavigation
+}: {
+ children: ReactNode
+ headings?: HeadingType[]
+ noProse?: boolean
+ noRightNav?: boolean
+ isIndexPage?: boolean
+ editPath?: string
+ docsNavigation: ReactNode
+}) {
+ const contentRef = useRef(null)
+ const mq = useMediaQuery()
+ const pathname = usePathname()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+ {!!headings.length && !noRightNav && (
+
+ )}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/docs/components/docs/DocumentEditorDemo.tsx b/docs/components/docs/DocumentEditorDemo.tsx
index 35dacf5375b..4b75f1a3006 100644
--- a/docs/components/docs/DocumentEditorDemo.tsx
+++ b/docs/components/docs/DocumentEditorDemo.tsx
@@ -1,5 +1,7 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import React, { type ReactNode, useContext, useEffect, useMemo, useState } from 'react'
import { type DocumentFeatures } from '@keystone-6/fields-document/views'
import {
diff --git a/docs/components/docs/ExamplesList.tsx b/docs/components/docs/ExamplesList.tsx
deleted file mode 100644
index f63c6818dcf..00000000000
--- a/docs/components/docs/ExamplesList.tsx
+++ /dev/null
@@ -1,160 +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 Examples () {
- const mq = useMediaQuery()
- return (
-
-
- A basic Blog schema with Posts and Authors. Use this as a starting place for learning how to
- use Keystone. It’s also a starter for other feature projects.
-
-
- A basic Task Management app, with Tasks and People who can be assigned to tasks. Great for
- learning how to use Keystone. It’s also a starter for other feature projects.
-
-
- Shows you how to extend the Keystone GraphQL API with custom queries and mutations. Builds
- upon the Blog starter project.
-
-
- Demonstrates how to use default values for fields. Builds upon the Task Manager starter
- project.
-
-
- Implements virtual fields in a Keystone list. Builds on the Blog starter project.
-
-
- Illustrates how to configure document fields in your Keystone
- system and render their data in a frontend application. Builds on the Blog starter project.
-
-
- Shows you how to write tests against the GraphQL API to your Keystone system. Builds on the
- Authentication example project.
-
-
- Adds password-based authentication to the Task Manager starter project.
-
-
- Illustrates how to use the json field type.
-
-
- Adds a custom Admin UI view to a json field which provides a
- customised editing experience for users.
-
-
- Adds a custom field type based on the integer field type which lets
- users rate items on a 5-star scale.
-
-
- Adds a custom page in the Admin UI.
-
-
- Adds a custom logo component in the Admin UI.
-
-
- Adds a custom Navigation component to the Admin UI.
-
-
- Example to demonstrate customisation of Keystone's document field and document renderer.
-
-
- )
-}
diff --git a/docs/components/docs/FeaturedCard.tsx b/docs/components/docs/FeaturedCard.tsx
new file mode 100644
index 00000000000..e2ea1f60842
--- /dev/null
+++ b/docs/components/docs/FeaturedCard.tsx
@@ -0,0 +1,62 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { useId } from 'react'
+import { Well } from '../primitives/Well'
+import { Markdoc } from '../Markdoc'
+import { useMediaQuery } from '../../lib/media'
+import { type Gradient } from '../../keystatic/gradient-selector'
+import { RenderableTreeNode, type Tag } from '@markdoc/markdoc'
+
+export function FeaturedCard ({
+ label,
+ description,
+ href,
+ gradient = 'grad1',
+}: {
+ label: string
+ description: Tag | null
+ href: string
+ gradient?: Gradient
+}) {
+ const id = useId()
+ return (
+
+ {description?.children.map((child, i) => (
+
+ ))}
+
+ )
+}
+
+export function FullWidthCardContainer ({ children }: { children: React.ReactNode }) {
+ const mq = useMediaQuery()
+ return (
+
+ {children}
+
+ )
+}
+
+export function SplitCardContainer ({ children }: { children: React.ReactNode }) {
+ const mq = useMediaQuery()
+ return (
+
+ {children}
+
+ )
+}
diff --git a/docs/components/docs/GitHubExamplesCTA.tsx b/docs/components/docs/GitHubExamplesCTA.tsx
index e4753ef1f86..6902eb01e7a 100644
--- a/docs/components/docs/GitHubExamplesCTA.tsx
+++ b/docs/components/docs/GitHubExamplesCTA.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
import { Alert } from '../primitives/Alert'
import { Button } from '../primitives/Button'
diff --git a/docs/components/docs/Heading.tsx b/docs/components/docs/Heading.tsx
index 5ae725e74a7..28437f957b3 100644
--- a/docs/components/docs/Heading.tsx
+++ b/docs/components/docs/Heading.tsx
@@ -1,7 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import slugify from '@sindresorhus/slugify'
-import { jsx } from '@emotion/react'
import { type ReactNode } from 'react'
import { HeadingIdLink } from './CopyToClipboard'
diff --git a/docs/components/docs/Keystone5DocsCTA.tsx b/docs/components/docs/Keystone5DocsCTA.tsx
index d600e9365c4..c873c55f5a7 100644
--- a/docs/components/docs/Keystone5DocsCTA.tsx
+++ b/docs/components/docs/Keystone5DocsCTA.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Alert } from '../primitives/Alert'
diff --git a/docs/components/docs/KeystoneExperience.tsx b/docs/components/docs/KeystoneExperience.tsx
new file mode 100644
index 00000000000..3d49c8dc214
--- /dev/null
+++ b/docs/components/docs/KeystoneExperience.tsx
@@ -0,0 +1,217 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import Link from 'next/link'
+
+import { Bulb } from '../icons/Bulb'
+import { Content } from '../icons/Content'
+import { Code } from '../icons/Code'
+import { Organization } from '../icons/Organization'
+import { Video } from '../icons/Video'
+
+import { useMediaQuery } from '../../lib/media'
+import { Type } from '../primitives/Type'
+
+export function KeystoneExperience () {
+ const mq = useMediaQuery()
+ return (
+ <>
+
+ The Keystone Experience
+
+
+
+ Discover the vision behind Keystone and what it's like to work with. If you’ve just heard
+ of Keystone, start here first:
+
+
a': {
+ borderRadius: '1rem',
+ boxShadow: '0 0 5px var(--shadow)',
+ padding: '1.5rem',
+ color: 'var(--app-bg)',
+ transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease',
+ textDecoration: 'none !important',
+ '&:hover, &:focus': {
+ boxShadow: '0 7px 21px var(--shadow)',
+ transform: 'translateY(-4px)',
+ },
+ '& svg': {
+ height: '2rem',
+ },
+ },
+ })}
+ >
+
+
+
+ Video Intro →
+
+
+ Learn how Keystone’s leading a new generation of content management tools.
+
+
+
+
+
+ Why Keystone →
+
+
+ The makers. The vision. What’s in the box, and what you can build with it.
+
+
+
+
+ a': {
+ borderRadius: '1rem',
+ boxShadow: '0 0 5px var(--shadow)',
+ padding: '1.5rem',
+ color: 'var(--app-bg)',
+ transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease',
+ textDecoration: 'none !important',
+ '&:hover, &:focus': {
+ boxShadow: '0 7px 21px var(--shadow)',
+ transform: 'translateY(-4px)',
+ },
+ '& svg': {
+ height: '2rem',
+ },
+ },
+ })}
+ >
+
+
+
+ For Developers →
+
+
+ Built the way you’d want it made. Keystone fits with the tools you know and love.
+
+
+
+
+
+ For Editors →
+
+
+ The configurable editing environment you need to do your best work.
+
+
+
+
+
+ For Organisations →
+
+
+ Own your data. Start fast. Find your audience anywhere. Scale on your terms.
+
+
+
+ >
+ )
+}
diff --git a/docs/components/docs/Navigation.tsx b/docs/components/docs/Navigation.tsx
index 1deed57b8c9..31dcd68fb26 100644
--- a/docs/components/docs/Navigation.tsx
+++ b/docs/components/docs/Navigation.tsx
@@ -1,5 +1,7 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import {
type AnchorHTMLAttributes,
type ReactNode,
@@ -8,8 +10,7 @@ import {
useContext,
useMemo,
} from 'react'
-import { useRouter } from 'next/router'
-import { jsx } from '@emotion/react'
+import { usePathname } from 'next/navigation'
import Link from 'next/link'
import { useMediaQuery } from '../../lib/media'
@@ -39,11 +40,11 @@ export const NavContextProvider = ({ children }: { children: ReactNode }) => {
const expandSection = (title: string) => {
const isSectionAlreadyExpanded = !collapsedSections.includes(title)
if (!isSectionAlreadyExpanded) {
- setCollapsedSections(collapsedSections.filter(cs => cs !== title))
+ setCollapsedSections(collapsedSections.filter((cs) => cs !== title))
}
}
const isSectionCollapsed = (title: string) => {
- return collapsedSections.some(cs => cs === title)
+ return collapsedSections.some((cs) => cs === title)
}
return { isSectionCollapsed, collapseSection, expandSection }
@@ -66,7 +67,7 @@ type NavSectionProps = {
children: ReactNode
}
-function NavSection ({ title, children }: NavSectionProps) {
+export function NavSection ({ title, children }: NavSectionProps) {
const { isSectionCollapsed, collapseSection, expandSection } = useNavContext()
const isCollapsed = isSectionCollapsed(title)
return (
@@ -129,9 +130,9 @@ export function NavItem ({
alwaysVisible,
...props
}: NavItemProps) {
- const { asPath } = useRouter()
+ const pathname = usePathname()
const mq = useMediaQuery()
- const isActive = typeof _isActive !== 'undefined' ? _isActive : asPath === href
+ const isActive = typeof _isActive !== 'undefined' ? _isActive : pathname === href
const ctx = useHeaderContext()
const isMobileNavOpen = ctx ? ctx.mobileNavIsOpen : true
const desktopOpenState = ctx ? ctx.desktopOpenState : -1
@@ -162,8 +163,8 @@ type PrimaryNavItemProps = {
} & AnchorHTMLAttributes
export function PrimaryNavItem ({ href, children }: PrimaryNavItemProps) {
- const { asPath } = useRouter()
- const isActive = asPath === href
+ const pathname = usePathname()
+ const isActive = pathname === href
const ctx = useHeaderContext()
const isMobileNavOpen = ctx ? ctx.mobileNavIsOpen : true
const desktopOpenState = ctx ? ctx.desktopOpenState : -1
@@ -189,124 +190,10 @@ export function PrimaryNavItem ({ href, children }: PrimaryNavItemProps) {
)
}
-export function DocsNavigation () {
- return (
- //
-
- Docs Home
- Getting Started
- Walkthroughs
- Examples
-
- Overview
- Command Line
- Relationships
- Choosing a Database
-
- Database Migration New
-
-
- Query Filters Updated
-
-
- Hooks Updated
-
-
- Auth & Access Control New
-
-
- Images & Files New
-
-
- GraphQL Schema ExtensionNew
-
- Testing
- Document Fields
- Document Field Demo
- Virtual Fields
- Custom Fields
- {/* Disable placeholder for now */}
- {/*
- Custom Field Views
- */}
- Custom Admin UI Logo
- Custom Admin UI Pages
- Custom Admin UI Navigation
-
-
- Overview
- Config
- Lists
- Authentication
-
- Access Control Updated
-
-
- Hooks Updated
-
- Session
-
-
-
- Overview
- BigInt
- Calendar Day
- Checkbox
- Cloudinary Image
- Decimal
- Document
- File
- Float
- Image
- Integer
- JSON
- Multiselect
- Password
- Relationship
- Select
- Text
- Timestamp
- Virtual
-
-
-
- Overview
- getContext
- Query
- DB
-
-
-
-
- Overview Updated
-
-
- Query Filters Updated
-
-
-
- Telemetry
-
-
- //
- )
-}
-
-export function UpdatesNavigation () {
+export function DocsNavigation ({ docsNavigation }: { docsNavigation?: React.ReactNode }) {
return (
-
- Latest News
- Roadmap
-
+ {docsNavigation}
)
-}
+}
\ No newline at end of file
diff --git a/docs/components/docs/Sidebar.tsx b/docs/components/docs/Sidebar.tsx
index 3e27bec94e9..f5df3473295 100644
--- a/docs/components/docs/Sidebar.tsx
+++ b/docs/components/docs/Sidebar.tsx
@@ -1,17 +1,14 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { useMediaQuery } from '../../lib/media'
-import { DocsNavigation, UpdatesNavigation } from './Navigation'
+import { DocsNavigation } from './Navigation'
type SidebarProps = {
- isUpdatesPage?: boolean
+ docsNavigation?: React.ReactNode
}
-export function Sidebar ({ isUpdatesPage }: SidebarProps) {
+export function Sidebar ({ docsNavigation }: SidebarProps) {
const mq = useMediaQuery()
- const Navigation = isUpdatesPage ? UpdatesNavigation : DocsNavigation
return (
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 (
+
+ {navigationMap &&
+ navigationMap.map((group, i) => (
+
+ {/* No collapsible section for the first group */}
+ {i === 0 ? (
+ group.items.map((item, j) => )
+ ) : (
+
+ {group.items.map((item, j) => (
+
+ ))}
+
+ )}
+
+ ))}
+
+ )
+}
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) => (
+
+
+ {group.groupName}
+
+
+ {group.groupDescription.children.map((child, j) => (
+
+ ))}
+
+
+
+ {group.items.map((item, j) => (
+
+ ))}
+
+
+ ))}
+ >
+ )
+}
diff --git a/docs/components/docs/featured-docs/index.tsx b/docs/components/docs/featured-docs/index.tsx
new file mode 100644
index 00000000000..27b7a76e067
--- /dev/null
+++ b/docs/components/docs/featured-docs/index.tsx
@@ -0,0 +1,8 @@
+import { FeaturedDocsClient } from './client'
+
+import { getFeaturedDocsMap } from '../../../keystatic/get-featured-docs-map'
+
+export async function FeaturedDocs () {
+ const featuredDocsMap = await getFeaturedDocsMap()
+ return
+}
diff --git a/docs/components/docs/featured-examples/client.tsx b/docs/components/docs/featured-examples/client.tsx
new file mode 100644
index 00000000000..d6ca864b9fa
--- /dev/null
+++ b/docs/components/docs/featured-examples/client.tsx
@@ -0,0 +1,55 @@
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { Well } from '../../primitives/Well'
+import { useMediaQuery } from '../../../lib/media'
+import { Markdoc } from '../../Markdoc'
+import { type FeaturedExamples } from '.'
+import { Type } from '../../primitives/Type'
+
+export default function ExamplesList ({ featuredExamples }: { featuredExamples: FeaturedExamples }) {
+ if (!featuredExamples) return null
+ const mq = useMediaQuery()
+ return (
+ <>
+
+ {featuredExamples.label}
+
+
+ {!!featuredExamples.description && (
+
+ {featuredExamples.description.children.map((child, i) => (
+
+ ))}
+
+ )}
+
+
+ {featuredExamples.items.map(
+ (item, i) =>
+ !!item && (
+
+ {item.description.children.map((child, i) => (
+
+ ))}
+
+ )
+ )}
+
+ >
+ )
+}
diff --git a/docs/components/docs/featured-examples/index.tsx b/docs/components/docs/featured-examples/index.tsx
new file mode 100644
index 00000000000..91827782a14
--- /dev/null
+++ b/docs/components/docs/featured-examples/index.tsx
@@ -0,0 +1,41 @@
+import ClientComponent from './client'
+
+import { type Tag, transform } from '@markdoc/markdoc'
+import { reader } from '../../../keystatic/reader'
+import { baseMarkdocConfig } from '../../../markdoc/config'
+
+
+export type FeaturedExamples = Awaited>
+
+async function getFeaturedExamples () {
+ const featuredExamples = await reader.singletons.featuredExamples.read({
+ resolveLinkedFiles: true,
+ })
+
+ if (!featuredExamples) return null
+
+ // Get the rich text fields Markdoc-ready
+ const transformedFeaturedExamples = {
+ ...featuredExamples,
+ description: transform(featuredExamples.description.node, baseMarkdocConfig) as Tag,
+ items: await Promise.all(
+ featuredExamples.items.map(async (itemSlug) => {
+ const item = await reader.collections.examples.read(itemSlug, {
+ resolveLinkedFiles: true,
+ })
+ if (!item) return null
+ return {
+ ...item,
+ description: transform(item.description.node, baseMarkdocConfig) as Tag,
+ }
+ })
+ ),
+ }
+
+ return transformedFeaturedExamples
+}
+
+export async function FeaturedExamples () {
+ const featuredExamples = await getFeaturedExamples()
+ return
+}
diff --git a/docs/components/icons/ArrowR.tsx b/docs/components/icons/ArrowR.tsx
index bdd04951cdc..58c54013615 100644
--- a/docs/components/icons/ArrowR.tsx
+++ b/docs/components/icons/ArrowR.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function ArrowR ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Automated.tsx b/docs/components/icons/Automated.tsx
index e71212282d3..b5f2a0ff05f 100644
--- a/docs/components/icons/Automated.tsx
+++ b/docs/components/icons/Automated.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Automated ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Bulb.tsx b/docs/components/icons/Bulb.tsx
index 492dc94a249..a44fe5e24d6 100644
--- a/docs/components/icons/Bulb.tsx
+++ b/docs/components/icons/Bulb.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Cli.tsx b/docs/components/icons/Cli.tsx
index 32f52e7b826..e4dc5cba34f 100644
--- a/docs/components/icons/Cli.tsx
+++ b/docs/components/icons/Cli.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/ClientLogos.tsx b/docs/components/icons/ClientLogos.tsx
index f9a8269ca83..15b26d51a10 100644
--- a/docs/components/icons/ClientLogos.tsx
+++ b/docs/components/icons/ClientLogos.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Close.tsx b/docs/components/icons/Close.tsx
index 5bb8ed274ea..07b82994788 100644
--- a/docs/components/icons/Close.tsx
+++ b/docs/components/icons/Close.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Code.tsx b/docs/components/icons/Code.tsx
index 9a19d45b3a0..f229c42a0c5 100644
--- a/docs/components/icons/Code.tsx
+++ b/docs/components/icons/Code.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Code ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Content.tsx b/docs/components/icons/Content.tsx
index aee10580861..75d8d1774f6 100644
--- a/docs/components/icons/Content.tsx
+++ b/docs/components/icons/Content.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Content ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Copy.tsx b/docs/components/icons/Copy.tsx
index 395479a2269..659bbafd137 100644
--- a/docs/components/icons/Copy.tsx
+++ b/docs/components/icons/Copy.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Custom.tsx b/docs/components/icons/Custom.tsx
index 09d4a632a9c..ff401c1fe41 100644
--- a/docs/components/icons/Custom.tsx
+++ b/docs/components/icons/Custom.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Custom ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/DFATLogo.tsx b/docs/components/icons/DFATLogo.tsx
index b9c019076c4..73286d7170c 100644
--- a/docs/components/icons/DFATLogo.tsx
+++ b/docs/components/icons/DFATLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/DarkMode.tsx b/docs/components/icons/DarkMode.tsx
index d596067618c..fe4f4a6123c 100644
--- a/docs/components/icons/DarkMode.tsx
+++ b/docs/components/icons/DarkMode.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function DarkMode ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Docs.tsx b/docs/components/icons/Docs.tsx
index 507be8f2d54..d3e5e3f5b6f 100644
--- a/docs/components/icons/Docs.tsx
+++ b/docs/components/icons/Docs.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Download.tsx b/docs/components/icons/Download.tsx
index 4d412d827ba..2c6d7af0c3e 100644
--- a/docs/components/icons/Download.tsx
+++ b/docs/components/icons/Download.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Edit.tsx b/docs/components/icons/Edit.tsx
index 352b7742353..887cafeb91c 100644
--- a/docs/components/icons/Edit.tsx
+++ b/docs/components/icons/Edit.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Editor.tsx b/docs/components/icons/Editor.tsx
index d051ab3bcbc..c0ab1bc1f14 100644
--- a/docs/components/icons/Editor.tsx
+++ b/docs/components/icons/Editor.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/EnliticLogo.tsx b/docs/components/icons/EnliticLogo.tsx
index 20e8a884afb..358c610337a 100644
--- a/docs/components/icons/EnliticLogo.tsx
+++ b/docs/components/icons/EnliticLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/Filter.tsx b/docs/components/icons/Filter.tsx
index 03d01fa8040..be0557a9e2d 100644
--- a/docs/components/icons/Filter.tsx
+++ b/docs/components/icons/Filter.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Filter ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/FrontEndLogos.tsx b/docs/components/icons/FrontEndLogos.tsx
index 77b261aafb1..b8017322f93 100644
--- a/docs/components/icons/FrontEndLogos.tsx
+++ b/docs/components/icons/FrontEndLogos.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
import { type SVGAttributes } from 'react'
export function FrontEndLogos (props: SVGAttributes) {
diff --git a/docs/components/icons/GitHub.tsx b/docs/components/icons/GitHub.tsx
index bbc707308ec..a2b6343b456 100644
--- a/docs/components/icons/GitHub.tsx
+++ b/docs/components/icons/GitHub.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/GraphQl.tsx b/docs/components/icons/GraphQl.tsx
index eaa8514cf77..c5ccc76363b 100644
--- a/docs/components/icons/GraphQl.tsx
+++ b/docs/components/icons/GraphQl.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Hamburger.tsx b/docs/components/icons/Hamburger.tsx
index 9c872016c34..0aea9e51e70 100644
--- a/docs/components/icons/Hamburger.tsx
+++ b/docs/components/icons/Hamburger.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Keystone.tsx b/docs/components/icons/Keystone.tsx
index 7bc6102bfa7..757ffa20436 100644
--- a/docs/components/icons/Keystone.tsx
+++ b/docs/components/icons/Keystone.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Keystone ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Lab.tsx b/docs/components/icons/Lab.tsx
index 07bb90b898e..66f13792bb8 100644
--- a/docs/components/icons/Lab.tsx
+++ b/docs/components/icons/Lab.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/LightMode.tsx b/docs/components/icons/LightMode.tsx
index 75dedfd459d..17bcdb1aad6 100644
--- a/docs/components/icons/LightMode.tsx
+++ b/docs/components/icons/LightMode.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Link.tsx b/docs/components/icons/Link.tsx
index a1201710e21..d273c4f3cbe 100644
--- a/docs/components/icons/Link.tsx
+++ b/docs/components/icons/Link.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Migration.tsx b/docs/components/icons/Migration.tsx
index 680c728879c..53a414b7810 100644
--- a/docs/components/icons/Migration.tsx
+++ b/docs/components/icons/Migration.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Migration ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Nextjs.tsx b/docs/components/icons/Nextjs.tsx
index 26ad5ad3c21..832d03664fe 100644
--- a/docs/components/icons/Nextjs.tsx
+++ b/docs/components/icons/Nextjs.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Nope.tsx b/docs/components/icons/Nope.tsx
index 352cc2e58ae..5883de401e4 100644
--- a/docs/components/icons/Nope.tsx
+++ b/docs/components/icons/Nope.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Organization.tsx b/docs/components/icons/Organization.tsx
index e25dcd759af..73cadf67ff2 100644
--- a/docs/components/icons/Organization.tsx
+++ b/docs/components/icons/Organization.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Organization ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/PJohnsonLogo.tsx b/docs/components/icons/PJohnsonLogo.tsx
index 6b5820246ba..4fefab15211 100644
--- a/docs/components/icons/PJohnsonLogo.tsx
+++ b/docs/components/icons/PJohnsonLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/Postgres.tsx b/docs/components/icons/Postgres.tsx
index 76cc647b924..b7b0f002695 100644
--- a/docs/components/icons/Postgres.tsx
+++ b/docs/components/icons/Postgres.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/PrintBarLogo.tsx b/docs/components/icons/PrintBarLogo.tsx
index 2698927aa02..109d1e826d5 100644
--- a/docs/components/icons/PrintBarLogo.tsx
+++ b/docs/components/icons/PrintBarLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/Prisma.tsx b/docs/components/icons/Prisma.tsx
index 5ed97697ad4..4798e04e444 100644
--- a/docs/components/icons/Prisma.tsx
+++ b/docs/components/icons/Prisma.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Profile.tsx b/docs/components/icons/Profile.tsx
index 887f4336833..b489fc99c1c 100644
--- a/docs/components/icons/Profile.tsx
+++ b/docs/components/icons/Profile.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Quote.tsx b/docs/components/icons/Quote.tsx
index bf553389c59..6009c6c1e72 100644
--- a/docs/components/icons/Quote.tsx
+++ b/docs/components/icons/Quote.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Relational.tsx b/docs/components/icons/Relational.tsx
index 780f44d418a..0212ac43a47 100644
--- a/docs/components/icons/Relational.tsx
+++ b/docs/components/icons/Relational.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Relational ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Relationship.tsx b/docs/components/icons/Relationship.tsx
index afd2e8317f4..7a5d524a772 100644
--- a/docs/components/icons/Relationship.tsx
+++ b/docs/components/icons/Relationship.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Roadmap.tsx b/docs/components/icons/Roadmap.tsx
index f07c6b24e4a..cf5e343307b 100644
--- a/docs/components/icons/Roadmap.tsx
+++ b/docs/components/icons/Roadmap.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Roadmap ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/RugbyAuLogo.tsx b/docs/components/icons/RugbyAuLogo.tsx
index a3c6c2b3680..ebf209c9957 100644
--- a/docs/components/icons/RugbyAuLogo.tsx
+++ b/docs/components/icons/RugbyAuLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/Search.tsx b/docs/components/icons/Search.tsx
index 03ca23036e5..95bded21e11 100644
--- a/docs/components/icons/Search.tsx
+++ b/docs/components/icons/Search.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/SearchKeys.tsx b/docs/components/icons/SearchKeys.tsx
index d8d41d5059f..4c1e1937a74 100644
--- a/docs/components/icons/SearchKeys.tsx
+++ b/docs/components/icons/SearchKeys.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Shield.tsx b/docs/components/icons/Shield.tsx
index b4c3d2547ec..c5739afcfb2 100644
--- a/docs/components/icons/Shield.tsx
+++ b/docs/components/icons/Shield.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Shield ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Slack.tsx b/docs/components/icons/Slack.tsx
index fa0a21f1fde..3755aafe45a 100644
--- a/docs/components/icons/Slack.tsx
+++ b/docs/components/icons/Slack.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Thinkmill.tsx b/docs/components/icons/Thinkmill.tsx
index 421441c93aa..b66006d006e 100644
--- a/docs/components/icons/Thinkmill.tsx
+++ b/docs/components/icons/Thinkmill.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Thinkmill ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Tick.tsx b/docs/components/icons/Tick.tsx
index 2eab84ed34e..0d7c52587ce 100644
--- a/docs/components/icons/Tick.tsx
+++ b/docs/components/icons/Tick.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Tick ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Twitter.tsx b/docs/components/icons/Twitter.tsx
index 9a529750165..9bef79a4f07 100644
--- a/docs/components/icons/Twitter.tsx
+++ b/docs/components/icons/Twitter.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Typescript.tsx b/docs/components/icons/Typescript.tsx
index ad4d5494b0e..a962b36d41d 100644
--- a/docs/components/icons/Typescript.tsx
+++ b/docs/components/icons/Typescript.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Typescript ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Updates.tsx b/docs/components/icons/Updates.tsx
index 986da021050..e966bbfd075 100644
--- a/docs/components/icons/Updates.tsx
+++ b/docs/components/icons/Updates.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
import { Gradients, type IconProps } from './util'
export function Updates ({ grad, ...props }: IconProps) {
diff --git a/docs/components/icons/Video.tsx b/docs/components/icons/Video.tsx
index 0af6dcfaa71..1ca61d2e923 100644
--- a/docs/components/icons/Video.tsx
+++ b/docs/components/icons/Video.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/VocalLogo.tsx b/docs/components/icons/VocalLogo.tsx
index 36bdd7b15f2..19696915365 100644
--- a/docs/components/icons/VocalLogo.tsx
+++ b/docs/components/icons/VocalLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/Watch.tsx b/docs/components/icons/Watch.tsx
index 2c9b91b095f..8410eff04e2 100644
--- a/docs/components/icons/Watch.tsx
+++ b/docs/components/icons/Watch.tsx
@@ -1,6 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/Welcome.tsx b/docs/components/icons/Welcome.tsx
index 51111ace2f1..2911aa729d9 100644
--- a/docs/components/icons/Welcome.tsx
+++ b/docs/components/icons/Welcome.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/WestpacLogo.tsx b/docs/components/icons/WestpacLogo.tsx
index 77ff209634a..e56819ddb0f 100644
--- a/docs/components/icons/WestpacLogo.tsx
+++ b/docs/components/icons/WestpacLogo.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { type IconProps } from './util'
diff --git a/docs/components/icons/WhyKeystone.tsx b/docs/components/icons/WhyKeystone.tsx
index 29bc205559d..eacfa8aebfb 100644
--- a/docs/components/icons/WhyKeystone.tsx
+++ b/docs/components/icons/WhyKeystone.tsx
@@ -1,6 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/YouTube.tsx b/docs/components/icons/YouTube.tsx
index 21ae20f20f2..601feef4fc7 100644
--- a/docs/components/icons/YouTube.tsx
+++ b/docs/components/icons/YouTube.tsx
@@ -1,5 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@emotion/react'
import { Gradients, type IconProps } from './util'
diff --git a/docs/components/icons/util.tsx b/docs/components/icons/util.tsx
index d54907ad37a..499278727e3 100644
--- a/docs/components/icons/util.tsx
+++ b/docs/components/icons/util.tsx
@@ -1,6 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
import { type SVGAttributes } from 'react'
export type IconGradient = 'grad1' | 'grad2' | 'grad3' | 'grad4' | 'grad5' | 'grad6' | 'logo'
diff --git a/docs/components/primitives/Alert.tsx b/docs/components/primitives/Alert.tsx
index 875126e597f..18870682a67 100644
--- a/docs/components/primitives/Alert.tsx
+++ b/docs/components/primitives/Alert.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
import classnames from 'classnames'
import { type HTMLAttributes } from 'react'
@@ -10,5 +8,5 @@ type AlertProps = {
export function Alert ({ look = 'neutral', className, ...props }: AlertProps) {
const classes = classnames('hint', look, className) // styles for this component can be found in the _app.js file
- return
+ return
}
diff --git a/docs/components/primitives/Badge.tsx b/docs/components/primitives/Badge.tsx
index 47aa03232e2..1f57305a4f1 100644
--- a/docs/components/primitives/Badge.tsx
+++ b/docs/components/primitives/Badge.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
const styleMap = {
diff --git a/docs/components/primitives/Button.tsx b/docs/components/primitives/Button.tsx
index 13e0e140dbe..e113f6aa71b 100644
--- a/docs/components/primitives/Button.tsx
+++ b/docs/components/primitives/Button.tsx
@@ -1,5 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
+/** @jsxImportSource @emotion/react */
+
import { type ReactNode } from 'react'
import { type CSSObject, jsx } from '@emotion/react'
import Link from 'next/link'
diff --git a/docs/components/primitives/Code.tsx b/docs/components/primitives/Code.tsx
index af46180cf64..85ef7ee72bf 100644
--- a/docs/components/primitives/Code.tsx
+++ b/docs/components/primitives/Code.tsx
@@ -1,7 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { Highlight, Prism } from 'prism-react-renderer'
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { Highlight, Prism, type LineOutputProps, type TokenOutputProps } from 'prism-react-renderer'
import { type ReactNode, useEffect, useMemo, useState } from 'react'
import theme from '../../lib/prism-theme'
@@ -12,7 +13,7 @@ type CollapseRange = Range & { isCollapsed: boolean }
const getRanges = (lines: string): Range[] => {
let ranges: Range[] = []
- lines.split(',').forEach(lineRange => {
+ lines.split(',').forEach((lineRange) => {
if (lineRange.length) {
const [range1, range2] = lineRange.split('-')
@@ -67,7 +68,7 @@ const parseClassName = (
return {
language: (language as any) || 'typescript',
highlightRanges: getRanges(highlights?.replace('}', '') || ''),
- collapseRanges: getRanges(collapses?.replace(']', '') || '').map(range => ({
+ collapseRanges: getRanges(collapses?.replace(']', '') || '').map((range) => ({
...range,
isCollapsed: true,
})),
@@ -119,7 +120,7 @@ export function Code ({ children, className }: { children: string, className?: s
{
- let updated = collapseState.map(item =>
+ let updated = collapseState.map((item) =>
item.start === i ? { ...item, isCollapsed: false } : item
)
@@ -142,10 +143,13 @@ export function Code ({ children, className }: { children: string, className?: s
return undefined
}
+ // Need to extract the key from lineProps, React not happy about spreading a key on an element
+ const lineProps = getLineProps({ line, key: i })
+ const { key, ...restLineProps } = lineProps
return (
+
+ // Need to extract the key from lineProps, React not happy about spreading a key on an element
+ const tokenProps = getTokenProps({ token, key })
+ const { key: k, ...restTokenProps } = tokenProps
+ return
})}
)
diff --git a/docs/components/primitives/EditButton.tsx b/docs/components/primitives/EditButton.tsx
index 85812ff9199..42fde7ba3db 100644
--- a/docs/components/primitives/EditButton.tsx
+++ b/docs/components/primitives/EditButton.tsx
@@ -1,6 +1,4 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
import { Edit } from '../../components/icons/Edit'
import { Button } from './Button'
@@ -14,12 +12,12 @@ export function EditButton ({
isIndexPage?: boolean
editPath?: string
}) {
- let fileUrl = `https://github.com/keystonejs/keystone/edit/main/docs/pages`
+ let fileUrl = `https://github.com/keystonejs/keystone/edit/main/docs`
if (editPath) {
- fileUrl += `/${editPath}`
+ fileUrl += `/content/${editPath}`
} else if (isIndexPage) {
- fileUrl += `${pathName}/index.tsx`
+ fileUrl += `/app/(site)${pathName}/page-client.tsx`
} else {
fileUrl += `${pathName}.md`
}
diff --git a/docs/components/primitives/Emoji.tsx b/docs/components/primitives/Emoji.tsx
index 9024d642641..4a5f6cc39a8 100644
--- a/docs/components/primitives/Emoji.tsx
+++ b/docs/components/primitives/Emoji.tsx
@@ -1,6 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx, keyframes } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { keyframes } from '@emotion/react'
import { useRef, useState, useEffect, type HTMLAttributes, type ReactNode } from 'react'
const fadeInTop = keyframes`
diff --git a/docs/components/primitives/Field.tsx b/docs/components/primitives/Field.tsx
index bfbf932b411..154f4aa366d 100644
--- a/docs/components/primitives/Field.tsx
+++ b/docs/components/primitives/Field.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type InputHTMLAttributes } from 'react'
import { Stack } from './Stack'
import { Type } from './Type'
diff --git a/docs/components/primitives/GitHubButton.tsx b/docs/components/primitives/GitHubButton.tsx
index c8e720bb28b..402a4c62d3a 100644
--- a/docs/components/primitives/GitHubButton.tsx
+++ b/docs/components/primitives/GitHubButton.tsx
@@ -1,6 +1,7 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
import { useState, useEffect, type HTMLAttributes } from 'react'
import { Loading } from './Loading'
diff --git a/docs/components/primitives/Gradient.tsx b/docs/components/primitives/Gradient.tsx
index d9f6be164c1..b797761ad84 100644
--- a/docs/components/primitives/Gradient.tsx
+++ b/docs/components/primitives/Gradient.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes, type ElementType } from 'react'
const styleMap = {
diff --git a/docs/components/primitives/Highlight.tsx b/docs/components/primitives/Highlight.tsx
index d6f99678a95..1e31261c13c 100644
--- a/docs/components/primitives/Highlight.tsx
+++ b/docs/components/primitives/Highlight.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes, type ElementType } from 'react'
const styleMap = {
diff --git a/docs/components/primitives/Loading.tsx b/docs/components/primitives/Loading.tsx
index f9c57ee0a8f..f5791f57c64 100644
--- a/docs/components/primitives/Loading.tsx
+++ b/docs/components/primitives/Loading.tsx
@@ -1,6 +1,8 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx, keyframes } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+'use client'
+
+import { keyframes } from '@emotion/react'
import { type HTMLAttributes } from 'react'
const loading = keyframes({
diff --git a/docs/components/primitives/SearchField.tsx b/docs/components/primitives/SearchField.tsx
index ad907d89cdf..bd82e1ca5f5 100644
--- a/docs/components/primitives/SearchField.tsx
+++ b/docs/components/primitives/SearchField.tsx
@@ -1,6 +1,6 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx, Global, css } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
+import { Global, css } from '@emotion/react'
import { Fragment, type HTMLAttributes } from 'react'
import { algoliaStyles } from '../../lib/algoliaStyles'
diff --git a/docs/components/primitives/Stack.tsx b/docs/components/primitives/Stack.tsx
index 9590c0ed373..05c5ea7b253 100644
--- a/docs/components/primitives/Stack.tsx
+++ b/docs/components/primitives/Stack.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import { type HTMLAttributes } from 'react'
import { SPACE } from '../../lib/TOKENS'
diff --git a/docs/components/primitives/Status.tsx b/docs/components/primitives/Status.tsx
index 42443c2967d..75dbb3cc0a7 100644
--- a/docs/components/primitives/Status.tsx
+++ b/docs/components/primitives/Status.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
const statusMap = {
notStarted: {
diff --git a/docs/components/primitives/Type.tsx b/docs/components/primitives/Type.tsx
index 6603d6abb68..61055844f5a 100644
--- a/docs/components/primitives/Type.tsx
+++ b/docs/components/primitives/Type.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+
+/** @jsxImportSource @emotion/react */
import { forwardRefWithAs } from '../../lib/forwardRefWithAs'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/primitives/Well.tsx b/docs/components/primitives/Well.tsx
index f12314020ad..693274d527f 100644
--- a/docs/components/primitives/Well.tsx
+++ b/docs/components/primitives/Well.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import Link from 'next/link'
import { type AnchorHTMLAttributes, type ReactNode } from 'react'
diff --git a/docs/components/primitives/Wrapper.tsx b/docs/components/primitives/Wrapper.tsx
index 6b79ff8baec..bcf118f0153 100644
--- a/docs/components/primitives/Wrapper.tsx
+++ b/docs/components/primitives/Wrapper.tsx
@@ -1,6 +1,5 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
+/** @jsxImportSource @emotion/react */
+
import type { ElementType, HTMLAttributes } from 'react'
import { useMediaQuery } from '../../lib/media'
diff --git a/docs/components/primitives/YouTubeEmbed.tsx b/docs/components/primitives/YouTubeEmbed.tsx
index 884646d5f3f..bba8079798a 100644
--- a/docs/components/primitives/YouTubeEmbed.tsx
+++ b/docs/components/primitives/YouTubeEmbed.tsx
@@ -1,7 +1,3 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
type YouTubeEmbedProps = {
url: string
label: string
diff --git a/docs/content/blog/embedded-mode-with-sqlite-nextjs.md b/docs/content/blog/embedded-mode-with-sqlite-nextjs.md
index f334151bca7..422564617c2 100644
--- a/docs/content/blog/embedded-mode-with-sqlite-nextjs.md
+++ b/docs/content/blog/embedded-mode-with-sqlite-nextjs.md
@@ -50,7 +50,7 @@ Here's what we're going to do:
Create a basic Next.js project with the `--typescript` option in an empty directory.
```bash
-yarn create next-app --typescript my-project
+npm create next-app --typescript my-project
cd my-project
```
@@ -71,7 +71,7 @@ It is recommended that you use the same major version of `next` as used internal
### Start your local server
-Run `yarn dev` at the root of your project.
+Run `npm run dev` at the root of your project.
Next.js will start a local server for you at
@@ -86,7 +86,7 @@ Now that we have the Next.js starter with static files, let‘s embed Keystone i
Add the following Keystone dependency to your project:
```bash
-yarn add @keystone-6/core
+npm install @keystone-6/core
```
### Update .gitignore
@@ -167,7 +167,7 @@ Finally, make a small change to the `scripts` object in `package.json` to includ
## Start all the things
-Running `yarn dev` again will do the following:
+Running `npm run dev` again will do the following:
- Provision a GraphQL schema based on the configuration of `keystone.ts`
- Build a [Prisma.io](https://www.prisma.io/) schema (which Keystone uses to manage the database)
@@ -288,7 +288,7 @@ export async function getStaticProps({ params }: GetStaticPropsContext) {
}
```
-Run `yarn dev` again.
+Run `npm run dev` again.
**Congratulations!** 🙌 You now have:
diff --git a/docs/content/blog/general-availability.md b/docs/content/blog/general-availability.md
index d99f704fd14..d29dfb0794e 100644
--- a/docs/content/blog/general-availability.md
+++ b/docs/content/blog/general-availability.md
@@ -53,7 +53,7 @@ Here's a few of the other cool things we shipped in Keystone this year:
- [JSON field](/docs/fields/json)
- [17 example projects](/docs/examples) to explore Keystone's many features and get you up and running on the web
-This release completes a body of work that make **Keystone 6 our best developer experience yet**. If you've been waiting to tryout Keystone 6 **there's never been a better time**. Just `yarn create keystone-app` or read our [getting started guide](/docs/getting-started) to take your first steps.
+This release completes a body of work that make **Keystone 6 our best developer experience yet**. If you've been waiting to tryout Keystone 6 **there's never been a better time**. Just `npm create keystone-app` or read our [getting started guide](/docs/getting-started) to take your first steps.
## What's Next
diff --git a/docs/content/blog/introducing-keystone-blog.md b/docs/content/blog/introducing-keystone-blog.md
index 66eaa0885be..74b8c23aa31 100644
--- a/docs/content/blog/introducing-keystone-blog.md
+++ b/docs/content/blog/introducing-keystone-blog.md
@@ -7,7 +7,7 @@ authorHandle: "https://twitter.com/flexdinesh"
metaImageUrl: ""
---
-We're happy to introduce the **Keystone Blog**, a buzzing corner for all the latest news and announcements about Keystone, brought to you by the Keystone team.
+We're happy to introduce the **Keystone Blog**, brought to you by the Keystone team.
Our _Updates_ page is going away and we are not one to let good content slip away. So some of the useful and important content in _Updates_ have been repurposed into blog posts. Woot!
diff --git a/docs/content/docs/config/config.md b/docs/content/docs/config/config.md
index d21ca962754..86a98c712a3 100644
--- a/docs/content/docs/config/config.md
+++ b/docs/content/docs/config/config.md
@@ -166,8 +166,6 @@ export default config({
{
mode: 'write',
src: `
- /** @jsxRuntime classic */
-/** @jsx jsx */
import { jsx } from '@keystone-ui/core';
export default function Welcome() {
return (Welcome to my Keystone system );
diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md
index 0068ad744c6..27d0259105e 100644
--- a/docs/content/docs/config/hooks.md
+++ b/docs/content/docs/config/hooks.md
@@ -9,7 +9,7 @@ The differences will be explicitly called out below.
For each hook, the fields hooks are applied to **all fields first** in parallel, followed by the list hooks.
-All hook functions are async and, with the exception of `resolveInput`, do not return a value.
+Hook functions support `async` and, with the exception of `resolveInput`, do not need a return value.
When operating on multiple values the hooks are called individually for each item being updated, created or deleted.
@@ -27,19 +27,44 @@ export default config({
create: async args => { /* ... */ },
update: async args => { /* ... */ },
},
- validateInput: async args => { /* ... */ },
- validateDelete: async args => { /* ... */ },
- beforeOperation: async args => { /* ... */ },
- afterOperation: async args => { /* ... */ },
+ validate: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ },
+ beforeOperation: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ },
+ afterOperation: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ }
},
fields: {
someFieldName: text({
hooks: {
- resolveInput: async args => { /* ... */ },
- validateInput: async args => { /* ... */ },
- validateDelete: async args => { /* ... */ },
- beforeOperation: async args => { /* ... */ },
- afterOperation: async args => { /* ... */ },
+ resolveInput: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ },
+ validate: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ },
+ beforeOperation: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ },
+ afterOperation: {
+ create: async args => { /* ... */ },
+ update: async args => { /* ... */ },
+ delete: async args => { /* ... */ },
+ }
},
}),
},
@@ -50,13 +75,13 @@ export default config({
### resolveInput
-The `resolveInput` function is used to modify or augment the `data` values passed in to a `create` or `update` operation.
+The `resolveInput` hook is a transform for mutating the input `data` value prior to calling any other successive hooks, as part of the operation.
This hook is the final stage in the [data resolving process](#resolved-data-stages), and is invoked after access control has been applied.
For field hooks, the return value should be an updated value for that specific field.
For list hooks, the return value should be a [`resolved data`](#resolved-data-stages) object.
-The result of `resolveInput` will be passed as `resolvedData` into the next stages of the operation.
+The result of `resolveInput` hooks is accessible as the argument `resolvedData` in the hooks that follow, for the remainder of the operation.
| Argument | Description |
| :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -65,7 +90,7 @@ The result of `resolveInput` will be passed as `resolvedData` into the next stag
| `operation` | The operation being performed (`'create'` or `'update'`). |
| `inputData` | The value of `data` passed into the mutation. |
| `item` | The currently stored item (`undefined` for `create` operations). This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. |
-| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after default values, relationship resolvers, and field resolvers have been applied. |
+| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after default values, relationship resolvers, field resolvers, and `resolveInput` hooks have been applied. |
| `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. |
```typescript
@@ -76,32 +101,59 @@ export default config({
lists: {
SomeListName: list({
hooks: {
- resolveInput: async ({
- listKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- }) => {
- /* ... */
- return resolvedData;
+ resolveInput: {
+ create: async ({
+ listKey,
+ operation, // always 'create'
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => {
+ /* ... */
+ return resolvedData;
+ },
+ update: async ({
+ listKey,
+ operation, // always 'update'
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => {
+ /* ... */
+ return resolvedData;
+ },
},
},
fields: {
someFieldName: text({
hooks: {
- resolveInput: async ({
- listKey,
- fieldKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- }) => {
- /* ... */
- return resolvedData[fieldKey];
+ resolveInput: {
+ create: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => {
+ /* ... */
+ return resolvedData[fieldKey];
+ },
+ update: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => {
+ /* ... */
+ return resolvedData[fieldKey];
+ },
},
},
}),
@@ -111,23 +163,26 @@ export default config({
});
```
-### validateInput
+### validate
+
+The `validate` hooks can be used to validate your [`resolvedData`](#resolved-data-stages) before a `create` or `update` operation completes, ensuring your expectations are met.
+This hook can additionally be used to check your expectations as part of a `delete` operation.
-The `validateInput` function is used to validate the [`resolvedData`](#resolved-data-stages) that will be saved during a `create` or `update` operation.
+For `create` and `update` operations, this hook is invoked after the respective `resolveInput` hooks has been run.
-It is invoked after the `resolveInput` hooks have been run.
+This hook should report any validation errors using the `addValidationError(message)` function, which is provided as a parameter.
+This is preferred to throwing to easily support more than one error message, if required.
-If the `resolvedData` is invalid then the function should report validation errors with `addValidationError(msg)`.
These error messages will be returned as a `ValidationFailureError` from the GraphQL API, and the operation will not be completed.
| Argument | Description |
| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `listKey` | The key of the list being operated on. |
| `fieldKey` | The key of the field being operated on (field hooks only). |
-| `operation` | The operation being performed (`'create'` or `'update'`). |
-| `inputData` | The value of `data` passed into the mutation. |
+| `operation` | The operation being performed (`'create'`, `'update'` or `'delete'`). |
+| `inputData` | The value of `data` passed into the mutation (`undefined` for `delete` operations). |
| `item` | The current value of the item being updated (`undefined` for `create` operations). This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. |
-| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after all data resolver stages have been completed. |
+| `resolvedData` | A [`resolved data`](#resolved-data-stages) object (`undefined` for `delete` operations). The resolved data value after all data resolver stages have been completed. |
| `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. |
| `addValidationError(msg)` | Used to set a validation error. |
@@ -139,82 +194,65 @@ export default config({
lists: {
SomeListName: list({
hooks: {
- validateInput: async ({
- listKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- addValidationError,
- }) => { /* ... */ },
- },
- fields: {
- someFieldName: text({
- hooks: {
- validateInput: async ({
- listKey,
- fieldKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- addValidationError,
- }) => { /* ... */ },
- },
- }),
- },
- }),
- },
-});
-```
-
-### validateDelete
-
-The `validateDelete` function is used during a `delete` operation to validate that deleting the selected item will not cause an issue in your system.
-
-It is invoked after access control has been applied.
-
-If the delete operation is invalid then the function should report validation errors with `addValidationError(msg)`.
-These error messages will be returned as a `ValidationFailureError` from the GraphQL API.
-
-| Argument | Description |
-| :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `listKey` | The key of the list being operated on. |
-| `fieldKey` | The key of the field being operated on (field hooks only). |
-| `operation` | The operation being performed (`'delete'`). |
-| `item` | The value of the item to be deleted. This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. |
-| `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. |
-| `addValidationError(msg)` | Used to set a validation error. |
-
-```typescript
-import { config, list } from '@keystone-6/core';
-import { text } from '@keystone-6/core/fields';
-
-export default config({
- lists: {
- SomeListName: list({
- hooks: {
- validateDelete: async ({
- listKey,
- operation,
- item,
- context,
- addValidationError,
- }) => { /* ... */ },
+ validate: {
+ create: async ({
+ listKey,
+ operation,
+ inputData,
+ resolvedData,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ operation,
+ item,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ },
},
fields: {
someFieldName: text({
hooks: {
- validateDelete: async ({
- listKey,
- fieldKey,
- operation,
- item,
- context,
- addValidationError,
- }) => { /* ... */ },
+ validate: {
+ create: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ resolvedData,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ fieldKey,
+ operation,
+ item,
+ context,
+ addValidationError,
+ }) => { /* ... */ },
+ },
},
}),
},
@@ -225,9 +263,9 @@ export default config({
### beforeOperation
-The `beforeOperation` function is used to perform side effects just before the data is saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations).
+The `beforeOperation` hook is used to perform side effects just before the data is saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations).
-It is invoked after all `validateInput`/`validateDelete` hooks have been run, but before the database is updated.
+It is invoked after the `resolveInput` and `validate` hooks, but before the database is updated by Prisma.
| Argument | Description |
| :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -247,27 +285,59 @@ export default config({
lists: {
SomeListName: list({
hooks: {
- beforeOperation: async ({
- listKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- }) => { /* ... */ },
+ beforeOperation: {
+ create: async ({
+ listKey,
+ operation,
+ inputData,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ operation,
+ item,
+ context,
+ }) => { /* ... */ },
+ },
},
fields: {
someFieldName: text({
hooks: {
- beforeOperation: async ({
- listKey,
- fieldKey,
- operation,
- inputData,
- item,
- resolvedData,
- context,
- }) => { /* ... */ },
+ beforeOperation: {
+ create: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ fieldKey,
+ operation,
+ item,
+ context,
+ }) => { /* ... */ },
+ },
},
}),
},
@@ -278,7 +348,7 @@ export default config({
### afterOperation
-The `afterOperation` function is used to perform side effects after the data has been saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations).
+The `afterOperation` hook is used to perform side effects after the data has been saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations).
| Argument | Description |
| :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -299,29 +369,63 @@ export default config({
lists: {
SomeListName: list({
hooks: {
- afterOperation: async ({
- listKey,
- operation,
- inputData,
- originalItem,
- item,
- resolvedData,
- context,
- }) => { /* ... */ },
+ afterOperation: {
+ create: async ({
+ listKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ operation,
+ inputData,
+ originalItem,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ operation,
+ originalItem,
+ context,
+ }) => { /* ... */ },
+ },
},
fields: {
someFieldName: text({
hooks: {
- afterOperation: async ({
- listKey,
- fieldKey,
- operation,
- inputData,
- originalItem,
- item,
- resolvedData,
- context,
- }) => { /* ... */ },
+ afterOperation: {
+ create: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ update: async ({
+ listKey,
+ fieldKey,
+ operation,
+ inputData,
+ originalItem,
+ item,
+ resolvedData,
+ context,
+ }) => { /* ... */ },
+ delete: async ({
+ listKey,
+ fieldKey,
+ operation,
+ originalItem,
+ context,
+ }) => { /* ... */ },
+ },
},
}),
},
diff --git a/docs/content/docs/getting-started.md b/docs/content/docs/getting-started.md
index 32b803d1ba5..ac8a415d562 100644
--- a/docs/content/docs/getting-started.md
+++ b/docs/content/docs/getting-started.md
@@ -11,9 +11,9 @@ It generates some files for you and installs all the dependencies you need to ru
## Quick Start
```sh
-yarn create keystone-app
+npm create keystone-app
cd my-app
-yarn dev
+npm run dev
```
## Installing a Keystone instance
@@ -21,41 +21,19 @@ yarn dev
Open your preferred shell and make sure you’re in the folder you want to create your new project in.
`create-keystone-app` will generate a new folder with your new Keystone files in it.
-### Yarn
-
-Use [`yarn create`](https://classic.yarnpkg.com/en/docs/cli/create/):
-
-```sh
-cd your/path/
-yarn create keystone-app
-```
-
### npm
-Use [`npm init`](https://docs.npmjs.com/cli/v7/commands/npm-init):
-
```sh
-cd your/path/
-npm init keystone-app@latest
+npm create keystone-app@latest
```
-
-{% hint kind="warn" %}
-`npm init ""` is available in npm 6+
-{% /hint %}
-
### npx
Use npm's [`npx`](https://docs.npmjs.com/cli/v7/commands/npx):
```sh
-cd your/path/
npx create-keystone-app@latest
```
-{% hint kind="warn" %}
-`npx` comes with npm 5.2+
-{% /hint %}
-
## Naming your app
The CLI will ask you to name your app. Once named, it will create a new SQLite database.
@@ -70,7 +48,7 @@ You can now `cd` into the folder that was created for you and start Keystone:
```sh
cd my-app
-yarn dev
+npm run dev
```
This will generate the Admin UI pages via [Next.js](https://nextjs.org/) on . When you visit the Admin UI for the first time you will be presented with a handy screen that asks you to create a user:
@@ -88,16 +66,16 @@ Keytone creates the following files in your newly generated folder. The most imp
```sh
.
-├── auth.ts # Authentication configuration for Keystone
-├── keystone.ts # The main entry file for configuring Keystone
-├── node_modules # Your dependencies
-├── package.json # Your package.json with four scripts prepared for you
-├── README.md # Additional info to help you get started
-├── schema.graphql # GraphQL schema (automatically generated by Keystone)
-├── schema.prisma # Prisma configuration (automatically generated by Keystone)
-├── schema.ts # Where you design your data schema
-├── tsconfig.json # Your typescript config
-└── yarn.lock # Your yarn lock file
+├── auth.ts # Authentication configuration for Keystone
+├── keystone.ts # The main entry file for configuring Keystone
+├── node_modules # Your dependencies
+├── package.json # Your package.json with four scripts prepared for you
+├── package-lock.json # Your npm lock file
+├── README.md # Additional info to help you get started
+├── schema.graphql # GraphQL schema (automatically generated by Keystone)
+├── schema.prisma # Prisma configuration (automatically generated by Keystone)
+├── schema.ts # Where you design your data schema
+└── tsconfig.json # Your typescript config
```
## Scripts
diff --git a/docs/content/docs/graphql/filters.md b/docs/content/docs/graphql/filters.md
index 90c726a1d2f..723a5a857ec 100644
--- a/docs/content/docs/graphql/filters.md
+++ b/docs/content/docs/graphql/filters.md
@@ -1,8 +1,10 @@
---
-title: "GraphQL Query Filters"
-description: "A reference list of every filters available for every Keystone field type. Keystone filters are typically named after the field they are filtering."
----
+title: GraphQL Query Filters
+description: >-
+ A reference list of every filters available for every Keystone field type.
+ Keystone filters are typically named after the field they are filtering.
+---
Each field type provides its own set of filters which can be used with [queries](./overview#users).
This page lists all the filters available for each field type.
For more details on how to use filters in queries please consult to the [GraphQL Queries - Filters](../guides/filters) guide.
@@ -11,36 +13,99 @@ For more details on how to use filters in queries please consult to the [GraphQL
### checkbox
-| **Filter name** | **Type** | **Description** |
-| --------------- | ----------------------- | ------------------------------- |
-| `equals` | `Boolean` | Equals |
-| `not` | `BooleanNullableFilter` | Does not match the inner filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `equals`
+- `Boolean`
+- Equals
+---
+- `not`
+- `BooleanNullableFilter`
+- Does not match the inner filter
+{% /table %}
### integer
-| **Filter name** | **Type** | **Description** |
-| --------------- | ------------------- | ------------------------------- |
-| `equals` | `Int` | Equals |
-| `lt` | `Int` | Less than |
-| `lte` | `Int` | Less than or equal |
-| `gt` | `Int` | Greater than |
-| `gte` | `Int` | Greater than or equal |
-| `in` | `[Int!]` | Is in the array |
-| `notIn` | `[Int!]` | Is not in the array |
-| `not` | `IntNullableFilter` | Does not match the inner filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `equals`
+- `Int`
+- Equals
+---
+- `lt`
+- `Int`
+- Less than
+---
+- `lte`
+- `Int`
+- Less than or equal
+---
+- `gt`
+- `Int`
+- Greater than
+---
+- `gte`
+- `Int`
+- Greater than or equal
+---
+- `in`
+- `[Int!]`
+- Is in the array
+---
+- `notIn`
+- `[Int!]`
+- Is not in the array
+---
+- `not`
+- `IntNullableFilter`
+- Does not match the inner filter
+{% /table %}
### bigInt
-| **Filter name** | **Type** | **Description** |
-| --------------- | ---------------------- | ------------------------------- |
-| `equals` | `BigInt` | Equals |
-| `lt` | `BigInt` | Less than |
-| `lte` | `BigInt` | Less than or equal |
-| `gt` | `BigInt` | Greater than |
-| `gte` | `BigInt` | Greater than or equal |
-| `in` | `[BigInt!]` | Is in the array |
-| `notIn` | `[BigInt!]` | Is not in the array |
-| `not` | `BigIntNullableFilter` | Does not match the inner filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `equals`
+- `BigInt`
+- Equals
+---
+- `lt`
+- `BigInt`
+- Less than
+---
+- `lte`
+- `BigInt`
+- Less than or equal
+---
+- `gt`
+- `BigInt`
+- Greater than
+---
+- `gte`
+- `BigInt`
+- Greater than or equal
+---
+- `in`
+- `[BigInt!]`
+- Is in the array
+---
+- `notIn`
+- `[BigInt!]`
+- Is not in the array
+---
+- `not`
+- `BigIntNullableFilter`
+- Does not match the inner filter
+{% /table %}
### json
@@ -48,51 +113,136 @@ The `json` field type does not support filters.
### float
-| **Filter name** | **Type** | **Description** |
-| --------------- | --------------------- | ------------------------------- |
-| `equals` | `Float` | Equals |
-| `lt` | `Float` | Less than |
-| `lte` | `Float` | Less than or equal |
-| `gt` | `Float` | Greater than |
-| `gte` | `Float` | Greater than or equal |
-| `in` | `[Float!]` | Is in the array |
-| `notIn` | `[Float!]` | Is not in the array |
-| `not` | `FloatNullableFilter` | Does not match the inner filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `equals`
+- `Float`
+- Equals
+---
+- `lt`
+- `Float`
+- Less than
+---
+- `lte`
+- `Float`
+- Less than or equal
+---
+- `gt`
+- `Float`
+- Greater than
+---
+- `gte`
+- `Float`
+- Greater than or equal
+---
+- `in`
+- `[Float!]`
+- Is in the array
+---
+- `notIn`
+- `[Float!]`
+- Is not in the array
+---
+- `not`
+- `FloatNullableFilter`
+- Does not match the inner filter
+{% /table %}
### password
-| **Filter name** | **Type** | **Description** |
-| --------------- | --------- | --------------- |
-| `isSet` | `Boolean` | A value is set |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `isSet`
+- `Boolean`
+- A value is set
+{% /table %}
### select
- If the `type` is `string`(the default), the same filters as `text` will be available.
- If the `type` is `integer`, the same filters as `integer` will be available.
- If the `type` is `enum`, the following filters will be available:
- \| **Filter name** \| **Type** \| **Description** \|
- \| --------------- \| ---------- \| ------------------- \|
- \| `equals` \| `ListKeyFieldKeyType` | Equals |
- \| `in` \| `[ListKeyFieldKeyType!]` | Is in the array |
- \| `notIn` \| `[ListKeyFieldKeyType!]` | Is not in the array |
- \| `not` \| `ListKeyFieldKeyTypeNullableFilter` | Does not match the inner filter |
+ | **Filter name** | **Type** | **Description** |
+ | --------------- | ---------- | ------------------- |
+ | `equals` | `ListKeyFieldKeyType` | Equals |
+ | `in` | `[ListKeyFieldKeyType!]` | Is in the array |
+ | `notIn` | `[ListKeyFieldKeyType!]` | Is not in the array |
+ | `not` | `ListKeyFieldKeyTypeNullableFilter` | Does not match the inner filter |
### text
-| **Filter name** | **Type** | **Description** | **Notes** |
-| --------------- | ---------------------------------------- | ----------------------------------------------------- | --------- |
-| `equals` | `String` | Equals | |
-| `lt` | `String` | Less than | |
-| `lte` | `String` | Less than or equal | |
-| `gt` | `String` | Greater than | |
-| `gte` | `String` | Greater than or equal | |
-| `contains` | `String` | Contains | [1] |
-| `startsWith` | `String` | Starts with | [1] |
-| `endsWith` | `String` | Ends with | [1] |
-| `in` | `[String!]` | Is in the array | |
-| `notIn` | `[String!]` | Is not in the array | |
-| `mode` | `QueryMode` (`default` or `insensitive`) | Whether the filters should be case insensitive or not | [2] |
-| `not` | `NestedStringNullableFilter` | Does not match the inner filter | |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+- **Notes**
+---
+- `equals`
+- `String`
+- Equals
+-
+---
+- `lt`
+- `String`
+- Less than
+-
+---
+- `lte`
+- `String`
+- Less than or equal
+-
+---
+- `gt`
+- `String`
+- Greater than
+-
+---
+- `gte`
+- `String`
+- Greater than or equal
+-
+---
+- `contains`
+- `String`
+- Contains
+- [1]
+---
+- `startsWith`
+- `String`
+- Starts with
+- [1]
+---
+- `endsWith`
+- `String`
+- Ends with
+- [1]
+---
+- `in`
+- `[String!]`
+- Is in the array
+-
+---
+- `notIn`
+- `[String!]`
+- Is not in the array
+-
+---
+- `mode`
+- `QueryMode` (`default` or `insensitive`)
+- Whether the filters should be case insensitive or not
+- [2]
+---
+- `not`
+- `NestedStringNullableFilter`
+- Does not match the inner filter
+-
+{% /table %}
#### Notes
@@ -101,16 +251,43 @@ The `json` field type does not support filters.
### timestamp
-| **Filter name** | **Type** | **Description** |
-| --------------- | ------------------------ | ------------------------------- |
-| `equals` | `String` | Equals |
-| `lt` | `String` | Less than |
-| `lte` | `String` | Less than or equal |
-| `gt` | `String` | Greater than |
-| `gte` | `String` | Greater than or equal |
-| `in` | `[String!]` | Is in the array |
-| `notIn` | `[String!]` | Is not in the array |
-| `not` | `DateTimeNullableFilter` | Does not match the inner filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `equals`
+- `String`
+- Equals
+---
+- `lt`
+- `String`
+- Less than
+---
+- `lte`
+- `String`
+- Less than or equal
+---
+- `gt`
+- `String`
+- Greater than
+---
+- `gte`
+- `String`
+- Greater than or equal
+---
+- `in`
+- `[String!]`
+- Is in the array
+---
+- `notIn`
+- `[String!]`
+- Is not in the array
+---
+- `not`
+- `DateTimeNullableFilter`
+- Does not match the inner filter
+{% /table %}
## Relationship type
@@ -118,17 +295,35 @@ The `json` field type does not support filters.
#### many: true
-| **Filter name** | **Type** | **Description** |
-| --------------- | --------------- | ------------------------------------------ |
-| `every` | `FooWhereInput` | All related items match the nested filter |
-| `some` | `FooWhereInput` | Some related items match the nested filter |
-| `none` | `FooWhereInput` | No related items match the nested filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `every`
+- `FooWhereInput`
+- All related items match the nested filter
+---
+- `some`
+- `FooWhereInput`
+- Some related items match the nested filter
+---
+- `none`
+- `FooWhereInput`
+- No related items match the nested filter
+{% /table %}
#### many: false
-| **Filter name** | **Type** | **Description** |
-| --------------- | --------------- | ------------------------- |
-| `foo` | `FooWhereInput` | Matches the nested filter |
+{% table %}
+- **Filter name**
+- **Type**
+- **Description**
+---
+- `foo`
+- `FooWhereInput`
+- Matches the nested filter
+{% /table %}
## Virtual type
@@ -150,8 +345,10 @@ The `image` field type does not support filters.
{% related-content %}
{% well
-heading="Query Filters Guide"
-href="/docs/guides/filters" %}
+ heading="Query Filters Guide"
+ grad="grad1"
+ href="/docs/guides/filters"
+ target="" %}
Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will show you how to use filters to get the data you need from your system.
{% /well %}
{% /related-content %}
diff --git a/docs/content/docs/guides/cli.md b/docs/content/docs/guides/cli.md
index b4bc02ecf8f..9458e3bae6c 100644
--- a/docs/content/docs/guides/cli.md
+++ b/docs/content/docs/guides/cli.md
@@ -1,8 +1,9 @@
---
-title: "Command Line"
-description: "Learn how to use Keystone's command line interface (CLI) to develop, build, and deploy your Keystone projects."
+title: Command Line
+description: >-
+ Learn how to use Keystone's command line interface (CLI) to develop, build,
+ and deploy your Keystone projects.
---
-
Keystone's command line interface (CLI) has been designed to help you develop, build, and deploy your Keystone project.
Using the `keystone` command you can start the dev process, build and run your app in production, and control how you migrate the structure of your database as your schema changes.
@@ -125,6 +126,7 @@ This is typically useful early in a project's development lifecycle, when you wa
```bash
$ keystone postinstall
```
+
{% hint kind="tip" %}
Note: `postinstall` is an alias for `keystone build --no-ui --frozen` we recommend switching to this `build` command
{% /hint %}
@@ -142,6 +144,7 @@ While the recommended way to fix this problem is to start your app using `keysto
```bash
$ keystone postinstall --fix
```
+
{% hint kind="tip" %}
Note: `postinstall --fix` is an alias for `keystone build --no-ui` we recommend switching to this `build` command
{% /hint %}
@@ -155,7 +158,9 @@ $ keystone build
This command generates the files needed for Keystone to start in **production** mode. You should run it during the build phase of your production deployment.
It will also validate that the generated files you should have committed to source control are in sync with your Keystone Schema.
+
### build flags
+
- `--frozen` - Don't update the graphql or prisma schemas, only validate them, exits with error if the schemas don't match what keystone would generate.
- `--no-prisma` - Don't build or validate the prisma schema
- `--no-ui` - Don't build the AdminUI
@@ -169,7 +174,9 @@ $ keystone start
This command starts Keystone in **production** mode. It requires a build to have been generated (see `build` above).
It will not generate or apply any database migrations - these should be run during the **build** or **release** phase of your production deployment.
+
### start flags
+
- `--with-migrations` - Trigger prisma to run migrations as part of startup
- `--no-ui` - Don't serve the AdminUI
@@ -220,7 +227,7 @@ Most application hosting platforms allow you to specify a `build` script (for ne
Install the project dependencies, generate the build and deploy migrations:
```bash
-yarn && yarn keystone build && yarn keystone prisma migrate deploy
+npm i && npx keystone build && npx keystone prisma migrate deploy
```
{% hint kind="error" %}
@@ -232,8 +239,9 @@ Note: it is only safe to run migrations in the build step if deploys are built s
Start Keystone in production mode:
```bash
-yarn keystone start
+npx keystone start
```
+
{% hint kind="tip" %}
Note: To run migrations before you start Keystone use `keystone start --with-migrations`
{% /hint %}
@@ -249,8 +257,10 @@ Note: To run migrations before you start Keystone use `keystone start --with-mig
{% related-content %}
{% well
-heading="Getting Started with create-keystone-app"
-href="/docs/getting-started" %}
+ heading="Getting Started with create-keystone-app"
+ grad="grad1"
+ href="/docs/getting-started"
+ target="" %}
How to use Keystone's CLI app to standup a new local project with an Admin UI & the GraphQL Playground.
{% /well %}
{% /related-content %}
diff --git a/docs/content/docs/guides/custom-admin-ui-logo.md b/docs/content/docs/guides/custom-admin-ui-logo.md
index 50e76c2c006..a034017659a 100644
--- a/docs/content/docs/guides/custom-admin-ui-logo.md
+++ b/docs/content/docs/guides/custom-admin-ui-logo.md
@@ -27,8 +27,7 @@ If you have styling constraints, we recommend using the jsx export from the `@ke
```tsx
// admin/config.tsx
-/** @jsxRuntime classic */
-/** @jsx jsx */
+
import { jsx } from '@keystone-ui/core';
function CustomLogo () {
diff --git a/docs/content/docs/guides/custom-admin-ui-pages.md b/docs/content/docs/guides/custom-admin-ui-pages.md
index add8bc37a4f..1ae24fd08dd 100644
--- a/docs/content/docs/guides/custom-admin-ui-pages.md
+++ b/docs/content/docs/guides/custom-admin-ui-pages.md
@@ -154,8 +154,6 @@ The snippet below uses the emotion `jsx` runtime exported from `@keystone-ui/cor
```tsx
// admin/pages/custom-page.tsx
/** @jsxRuntime classic */
-/** @jsxRuntime classic */
-/** @jsx jsx */
import Link from 'next/link';
import { jsx } from '@keystone-ui/core';
diff --git a/docs/content/docs/guides/relationships.md b/docs/content/docs/guides/relationships.md
index d4d1a37ef45..52a09381c9f 100644
--- a/docs/content/docs/guides/relationships.md
+++ b/docs/content/docs/guides/relationships.md
@@ -1,8 +1,10 @@
---
-title: "Understanding Relationships"
-description: "Learn how to reason about and configure relationships in Keystone so you can bring value to your project through structured content."
----
+title: Understanding Relationships
+description: >-
+ Learn how to reason about and configure relationships in Keystone so you can
+ bring value to your project through structured content.
+---
Relationships are the connections you make between different lists of content in Keystone. What you build depends a great deal on what you need. This guide will show you how to reason about, and configure relationships in Keystone so you can bring value to your project through structured content.
---
@@ -15,7 +17,7 @@ Keystone provides a lot of flexibility when it comes to relationships. To get wh
Your needs here will define whether your relationship needs to be [one, or two-sided](#one-sided-and-two-sided-relationships).
-2. **How many connections do I need on either side of my relationship?**
+1. **How many connections do I need on either side of my relationship?**
Understanding this will determine the kind of [cardinality](#establishing-cardinality) you need to configure.
@@ -44,12 +46,12 @@ export default config({
```
{% hint kind="tip" %}
-The `many` config option relates to _cardinality_ which we explore later
+The `many` config option relates to *cardinality* which we explore later
{% /hint %}
## One-sided & two-sided relationships
-In Keystone it’s possible to define relationships from one, or both sides of the two lists you’re connecting. We refer to these as _one-sided_, and _two-sided_ relationships:
+In Keystone it’s possible to define relationships from one, or both sides of the two lists you’re connecting. We refer to these as *one-sided*, and *two-sided* relationships:
### One-sided
@@ -126,7 +128,7 @@ Two-sided relationships are declared in two places, but **there is only one rela
### Self-referencing relationships
-Keystone also lets you define one, and two-sided **relationships that refer to the same list**. To make a _one-sided_ Twitter style following relationship we do the following:
+Keystone also lets you define one, and two-sided **relationships that refer to the same list**. To make a *one-sided* Twitter style following relationship we do the following:
```typescript{9}[1-3]
import { config, list } from '@keystone-6/core';
@@ -144,7 +146,7 @@ export default config({
});
```
-Or change this into a _two-sided_ relationship to also access the followers of every user:
+Or change this into a *two-sided* relationship to also access the followers of every user:
```typescript{9-10}[1-3]
import { config, list } from '@keystone-6/core';
@@ -169,17 +171,28 @@ The only relationship configuration not currently supported is having a field re
## Establishing cardinality
-_Cardinality_ is a term used to describe how many items can exist on either side of a relationship. Each side can have either `one` or `many` related items. Since each relationship can have one and two sides, we have the following options available:
+*Cardinality* is a term used to describe how many items can exist on either side of a relationship. Each side can have either `one` or `many` related items. Since each relationship can have one and two sides, we have the following options available:
-| Relationship type | One to one | One to many | Many to many |
-| ----------------- | -------------------------------------------- | ---------------------------------------- | ---------------------------------------- |
-| One-sided | {% emoji symbol="❌" alt="Not supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} |
-| Two-sided | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} |
+{% table %}
+- Relationship type
+- One to one
+- One to many
+- Many to many
+---
+- One-sided
+- {% emoji symbol="❌" alt="Not supported" /%}
+- {% emoji symbol="✅" alt="Supported" /%}
+- {% emoji symbol="✅" alt="Supported" /%}
+---
+- Two-sided
+- {% emoji symbol="✅" alt="Supported" /%}
+- {% emoji symbol="✅" alt="Supported" /%}
+- {% emoji symbol="✅" alt="Supported" /%}
+{% /table %}
Cardinality is defined through the `relationship` field’s `many` configuration option. It’s a boolean where:
- `false` = one
-
- `true` = many
{% hint kind="tip" %}
@@ -350,8 +363,10 @@ Keystone relationships are managed using the [relationship](../fields/relationsh
{% related-content %}
{% well
-heading="Relationship Field API Reference"
-href="/docs/fields/relationship" %}
+ heading="Relationship Field API Reference"
+ grad="grad1"
+ href="/docs/fields/relationship"
+ target="" %}
Defines the names, types, and configuration of Keystone fields. See all the fields and the configuration options they accept.
{% /well %}
{% /related-content %}
diff --git a/docs/content/docs/guides/schema-extension.md b/docs/content/docs/guides/schema-extension.md
index 7e96205dbfc..27280bb413d 100644
--- a/docs/content/docs/guides/schema-extension.md
+++ b/docs/content/docs/guides/schema-extension.md
@@ -65,7 +65,7 @@ As `extendGraphqlSchema` expects a function that returns a valid GraphQL schema
Start by installing `@graphql-tools/schema`
```bash
-yarn add @graphql-tools/schema
+npm install @graphql-tools/schema
```
Then import into your Keystone configuration
diff --git a/docs/content/docs/walkthroughs/lesson-1.md b/docs/content/docs/walkthroughs/lesson-1.md
index 8debe44867c..d1af920df81 100644
--- a/docs/content/docs/walkthroughs/lesson-1.md
+++ b/docs/content/docs/walkthroughs/lesson-1.md
@@ -1,8 +1,7 @@
---
-title: "Lesson 1: Installing Keystone"
-description: "Learn Keystone: Lesson 1"
+title: 'Lesson 1: Installing Keystone'
+description: 'Learn Keystone: Lesson 1'
---
-
Learn how to install Keystone, create your first content type, and get an app up and running with an intuitive editing environment.
## Introduction
@@ -29,17 +28,17 @@ Let’s start by setting up a workspace for a new Keystone project. Make a new f
```bash
mkdir keystone-learning
cd keystone-learning
-yarn init
+npm init
```
{% hint kind="tip" %}
-We’ll be using `yarn` for installing packages, but you can use `npm` or any other package manager you prefer.
+We’ll be using `npm` for installing packages, but you can use any other package manager you prefer.
{% /hint %}
Now add the Keystone package:
```bash
-yarn add @keystone-6/core
+npm install @keystone-6/core
```
## Configure Keystone
@@ -54,7 +53,7 @@ export default {};
And add TypeScript as a dependency:
```bash
-yarn add typescript
+npm install typescript
```
{% hint kind="tip" %}
@@ -65,10 +64,10 @@ Your folder structure should now look like this:
```sh
.
-├── node_modules # Dependencies
-├── keystone.ts # Keystone config
-├── package.json # With Keystone and TypeScript as dependencies
-└── yarn.lock # Your yarn lock file
+├── node_modules # Dependencies
+├── keystone.ts # Keystone config
+├── package.json # With Keystone and TypeScript as dependencies
+└── package-lock.json # Your npm lock file
```
We now need to configure `keystone.ts` with two parts to get our project running:
@@ -143,14 +142,14 @@ are required, and declared that emails must be unique, so there can only be one
We now have everything we need to start Keystone, so let’s do just that:
```bash
-yarn keystone dev
+npx keystone dev
```
-In a few seconds your terminal will provide you with you a link to the Keystone Admin UI at
+In a few seconds your terminal will provide you with you a link to the Keystone Admin UI at [http://localhost:3000](http://localhost:3000)
![Terminal dialog showing successful Keystone startup](https://keystonejs.s3.amazonaws.com/framework-assets/assets/walkthroughs/lesson-1/keystone-startup.png)
-Head on over to where you can create your first user with a `name` and `email`:
+Head on over to [http://localhost:3000/users](http://localhost:3000/users) where you can create your first user with a `name` and `email`:
![Adding a user record in Keystone Admin UI](https://keystonejs.s3.amazonaws.com/framework-assets/assets/walkthroughs/lesson-1/first-user-creation.gif)
@@ -161,7 +160,11 @@ Next up, we’ll level-up our blog starter with a `post` list and connect it to
## Next lesson
{% related-content %}
-{% well heading="Lesson 2: Relating things" href="/docs/walkthroughs/lesson-2" %}
+{% well
+ heading="Lesson 2: Relating things"
+ grad="grad1"
+ href="/docs/walkthroughs/lesson-2"
+ target="" %}
Connect two content types and learn how to configure the appearance of field inputs
{% /well %}
{% /related-content %}
diff --git a/docs/content/docs/walkthroughs/lesson-2.md b/docs/content/docs/walkthroughs/lesson-2.md
index b9eb96ece18..fd8183e53c2 100644
--- a/docs/content/docs/walkthroughs/lesson-2.md
+++ b/docs/content/docs/walkthroughs/lesson-2.md
@@ -1,8 +1,7 @@
---
-title: "Lesson 2: Creating relationships"
-description: "Learn Keystone: Lesson 2"
+title: 'Lesson 2: Creating relationships'
+description: 'Learn Keystone: Lesson 2'
---
-
Learn how to connect two content types to each other and configure how you make those connections in Admin UI.
## Where we left off
@@ -267,8 +266,10 @@ export default config({
{% related-content %}
{% well
-heading="Lesson 3: Publishing workflows"
-href="/docs/walkthroughs/lesson-3" %}
+ heading="Lesson 3: Publishing workflows"
+ grad="grad1"
+ href="/docs/walkthroughs/lesson-3"
+ target="" %}
Support publishing needs with Keystone's `select` and
`timestamp` fields
{% /well %}
diff --git a/docs/content/docs/walkthroughs/lesson-3.md b/docs/content/docs/walkthroughs/lesson-3.md
index 372c26b4d99..829785cfba6 100644
--- a/docs/content/docs/walkthroughs/lesson-3.md
+++ b/docs/content/docs/walkthroughs/lesson-3.md
@@ -1,8 +1,8 @@
---
-title: "Lesson 3: Publishing workflows"
-description: "Learn Keystone: Lesson 3"
----
+title: 'Lesson 3: Publishing workflows'
+description: 'Learn Keystone: Lesson 3'
+---
Learn how to create a publishing workflow to your app using Keystons’s `select` and `timestamp` fields.
## Where we left off
@@ -294,7 +294,11 @@ export default config({
## Next lesson
{% related-content %}
-{% well heading="Lesson 4: Auth & Sessions" href="/docs/walkthroughs/lesson-4" %}
+{% well
+ heading="Lesson 4: Auth & Sessions"
+ grad="grad1"
+ href="/docs/walkthroughs/lesson-4"
+ target="" %}
Add sessions, password protection, and a sign-in screen to your Keystone app
{% /well %}
{% /related-content %}
diff --git a/docs/content/docs/walkthroughs/lesson-4.md b/docs/content/docs/walkthroughs/lesson-4.md
index 33db767623e..981d8441c5e 100644
--- a/docs/content/docs/walkthroughs/lesson-4.md
+++ b/docs/content/docs/walkthroughs/lesson-4.md
@@ -1,8 +1,8 @@
---
-title: "Lesson 4: Auth & Sessions"
-description: "Learn Keystone: Lesson 4"
----
+title: 'Lesson 4: Auth & Sessions'
+description: 'Learn Keystone: Lesson 4'
+---
Learn how to add passwords, session data and authentication to your Keystone app.
## Where we left off
@@ -105,7 +105,7 @@ That's all we need to store secure passwords in our database!
Authentication isn't built directly in to Keystone - it's an enhancement you can add on top. To use it in our app we need to add Keystone’s [auth package](https://github.com/keystonejs/keystone/tree/main/packages/auth):
```sh
-yarn add @keystone-6/auth
+npm install @keystone-6/auth
```
Now that we have the package, let’s create a new file in the root of our project to write our auth config in:
@@ -332,7 +332,11 @@ export default config(
## Next lesson
{% related-content %}
-{% well heading="Lesson 5: Rich Text" href="/docs/walkthroughs/lesson-5" %}
+{% well
+ heading="Lesson 5: Rich Text"
+ grad="grad1"
+ href="/docs/walkthroughs/lesson-5"
+ target="" %}
Add a powerful `document` field to your app and learn how to
configure it to meet your needs
{% /well %}
diff --git a/docs/content/docs/walkthroughs/lesson-5.md b/docs/content/docs/walkthroughs/lesson-5.md
index 1c4f0f13d0a..5a58bb133da 100644
--- a/docs/content/docs/walkthroughs/lesson-5.md
+++ b/docs/content/docs/walkthroughs/lesson-5.md
@@ -1,8 +1,7 @@
---
-title: "Lesson 5: Document field"
-description: "Learn Keystone: Lesson 5"
+title: 'Lesson 5: Document field'
+description: 'Learn Keystone: Lesson 5'
---
-
Learn how to implement a powerful and customisable Rich Text editing experience with Keystone’s `document` field.
## Where we left off
@@ -65,7 +64,7 @@ Keystone’s [document](https://keystonejs.com/docs/fields/document) field is a
To implement the document field we start by adding the package to our project:
```sh
-yarn add @keystone-6/fields-document
+npm install @keystone-6/fields-document
```
Next, we add the document field to our `post` list:
@@ -261,10 +260,15 @@ Congratulations! You've now built a Keystone app from an empty folder and have t
This lesson marks the end of this learning series. To dive deeper into Keystone's capabilities take a look at the following:
{% related-content %}
-{% well heading="Examples" href="/docs/examples" %}
+{% well heading="Examples" grad="grad1" href="/docs/examples" target="" %}
A growing collection of projects you can run locally to learn more about Keystone’s capabilities
{% /well %}
-{% well heading="Guides" href="/docs/guides/overview" %}
+
+{% well
+ heading="Guides"
+ grad="grad1"
+ href="/docs/guides/overview"
+ target="" %}
Practical explanations of Keystone's fundamental building blocks
{% /well %}
{% /related-content %}
diff --git a/docs/content/examples/auth.yaml b/docs/content/examples/auth.yaml
new file mode 100644
index 00000000000..d5983d8190b
--- /dev/null
+++ b/docs/content/examples/auth.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/auth
+title: Authentication
+kind: standalone
+description: >
+ Adds password-based authentication to the Task Manager starter project.
diff --git a/docs/content/examples/azure.yaml b/docs/content/examples/azure.yaml
new file mode 100644
index 00000000000..e5edf787e2e
--- /dev/null
+++ b/docs/content/examples/azure.yaml
@@ -0,0 +1,6 @@
+title: Microsoft Azure
+kind: deployment
+url: https://github.com/aaronpowell/keystone-6-azure-example
+description: >
+ Deploys a Keystone app backend to Microsoft Azure. Based on the `with-auth`
+ project. **One-click deployment** included.
diff --git a/docs/content/examples/blog.yaml b/docs/content/examples/blog.yaml
new file mode 100644
index 00000000000..fa9d654f263
--- /dev/null
+++ b/docs/content/examples/blog.yaml
@@ -0,0 +1,6 @@
+title: Blog
+kind: standalone
+url: https://github.com/keystonejs/keystone/tree/main/examples/usecase-blog
+description: >
+ A basic Blog schema with Posts and Authors. Use this as a starting place for
+ learning how to use Keystone. It’s also a starter for other feature projects.
diff --git a/docs/content/examples/custom-admin-ui-logo.yaml b/docs/content/examples/custom-admin-ui-logo.yaml
new file mode 100644
index 00000000000..d5bf1250891
--- /dev/null
+++ b/docs/content/examples/custom-admin-ui-logo.yaml
@@ -0,0 +1,5 @@
+title: Custom Admin UI Logo
+url: https://github.com/keystonejs/keystone/blob/main/examples/custom-admin-ui-logo
+kind: standalone
+description: >
+ Adds a custom logo component in the Admin UI.
diff --git a/docs/content/examples/custom-admin-ui-navigation.yaml b/docs/content/examples/custom-admin-ui-navigation.yaml
new file mode 100644
index 00000000000..f1439f3e10d
--- /dev/null
+++ b/docs/content/examples/custom-admin-ui-navigation.yaml
@@ -0,0 +1,5 @@
+title: Custom Admin UI Navigation
+url: https://github.com/keystonejs/keystone/tree/main/examples/custom-admin-ui-navigation
+kind: standalone
+description: >
+ Adds a custom Navigation component to the Admin UI.
diff --git a/docs/content/examples/custom-admin-ui-pages.yaml b/docs/content/examples/custom-admin-ui-pages.yaml
new file mode 100644
index 00000000000..0235df6f2b7
--- /dev/null
+++ b/docs/content/examples/custom-admin-ui-pages.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/custom-admin-ui-pages
+title: Custom Admin UI Pages
+kind: standalone
+description: >
+ Adds a custom page in the Admin UI.
diff --git a/docs/content/examples/custom-field-type.yaml b/docs/content/examples/custom-field-type.yaml
new file mode 100644
index 00000000000..7b05f00dbc4
--- /dev/null
+++ b/docs/content/examples/custom-field-type.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/custom-field
+title: Custom Field Type
+kind: standalone
+description: >
+ Adds a custom field type based on the `integer` field type which lets users rate items on a 5-star scale.
diff --git a/docs/content/examples/custom-field-view.yaml b/docs/content/examples/custom-field-view.yaml
new file mode 100644
index 00000000000..35b1c3dc32d
--- /dev/null
+++ b/docs/content/examples/custom-field-view.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/custom-field-view
+title: Custom Field View
+kind: standalone
+description: >
+ Adds a custom Admin UI view to a `json` field which provides a customised editing experience for users.
diff --git a/docs/content/examples/default-values.yaml b/docs/content/examples/default-values.yaml
new file mode 100644
index 00000000000..f9e0832f81f
--- /dev/null
+++ b/docs/content/examples/default-values.yaml
@@ -0,0 +1,8 @@
+title: Default Values
+kind: standalone
+url: https://github.com/keystonejs/keystone/tree/main/examples/default-values
+description: >
+ Demonstrates how to use default values for fields. Builds upon the Task
+ Manager starter
+
+ project.
diff --git a/docs/content/examples/document-field-customisation.yaml b/docs/content/examples/document-field-customisation.yaml
new file mode 100644
index 00000000000..2c1f2f0f5d0
--- /dev/null
+++ b/docs/content/examples/document-field-customisation.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/usecase-blog
+title: Document Field Customisation
+kind: end-to-end
+description: >
+ Example to demonstrate customisation of Keystone's document field and document renderer.
diff --git a/docs/content/examples/document-field.yaml b/docs/content/examples/document-field.yaml
new file mode 100644
index 00000000000..0ae9d24f6ce
--- /dev/null
+++ b/docs/content/examples/document-field.yaml
@@ -0,0 +1,6 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/document-field
+title: Document field
+kind: standalone
+description: >
+ Illustrates how to configure `document` fields in your
+ Keystone system and render their data in a frontend application.
diff --git a/docs/content/examples/extend-express-app.yaml b/docs/content/examples/extend-express-app.yaml
new file mode 100644
index 00000000000..c549b2565dd
--- /dev/null
+++ b/docs/content/examples/extend-express-app.yaml
@@ -0,0 +1,6 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/extend-express-app
+title: REST API endpoint
+kind: standalone
+description: >
+ Demonstrates how to create REST endpoints by extending Keystone's express app and using the
+ Query API to execute queries against the schema.
diff --git a/docs/content/examples/extend-graphql-schema-nexus.yaml b/docs/content/examples/extend-graphql-schema-nexus.yaml
new file mode 100644
index 00000000000..cf157b1d2cd
--- /dev/null
+++ b/docs/content/examples/extend-graphql-schema-nexus.yaml
@@ -0,0 +1,6 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-nexus
+title: Extend GraphQL API with Nexus
+kind: standalone
+description: >
+ Shows you how to use `Nexus` to extend the GraphQL API provided by Keystone
+ with custom queries and mutations.
diff --git a/docs/content/examples/extend-graphql-schema-ts.yaml b/docs/content/examples/extend-graphql-schema-ts.yaml
new file mode 100644
index 00000000000..8bbcd4db886
--- /dev/null
+++ b/docs/content/examples/extend-graphql-schema-ts.yaml
@@ -0,0 +1,6 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-ts
+title: Extend GraphQL Schema with GraphQL TS
+kind: standalone
+description: >
+ Demonstrates how to extend the GraphQL API provided by Keystone with custom queries and
+ mutations using graphql-ts.
diff --git a/docs/content/examples/heroku.yaml b/docs/content/examples/heroku.yaml
new file mode 100644
index 00000000000..0585f3802d9
--- /dev/null
+++ b/docs/content/examples/heroku.yaml
@@ -0,0 +1,6 @@
+title: Heroku
+kind: deployment
+url: https://github.com/keystonejs/keystone-6-heroku-example
+description: >
+ Based on the `with-auth` project, this example deploys a simple app backend to
+ Heroku. Includes a **one-click deployment** for Heroku account holders.
diff --git a/docs/content/examples/next-js.yaml b/docs/content/examples/next-js.yaml
new file mode 100644
index 00000000000..6aa9cea7fcd
--- /dev/null
+++ b/docs/content/examples/next-js.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/framework-nextjs-app-directory
+title: Next.js + Keystone
+kind: end-to-end
+description: >
+ Shows you how to use Keystone as a data engine within Next.js applications.
diff --git a/docs/content/examples/railway.yaml b/docs/content/examples/railway.yaml
new file mode 100644
index 00000000000..5d24141dbfc
--- /dev/null
+++ b/docs/content/examples/railway.yaml
@@ -0,0 +1,6 @@
+title: Railway
+kind: deployment
+url: https://github.com/keystonejs/keystone-6-railway-example
+description: >
+ Deploys a Keystone app backend to Railway. Based on the `with-auth` project.
+ **One-click deployment** included.
diff --git a/docs/content/examples/singleton.yaml b/docs/content/examples/singleton.yaml
new file mode 100644
index 00000000000..824427b184a
--- /dev/null
+++ b/docs/content/examples/singleton.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/singleton
+title: Singleton
+kind: standalone
+description: >
+ Demonstrates how to use singleton lists with Keystone.
diff --git a/docs/content/examples/task-manager.yaml b/docs/content/examples/task-manager.yaml
new file mode 100644
index 00000000000..2d72c0a1bb5
--- /dev/null
+++ b/docs/content/examples/task-manager.yaml
@@ -0,0 +1,7 @@
+title: Task Manager
+kind: standalone
+url: https://github.com/keystonejs/keystone/tree/main/examples/usecase-todo
+description: >
+ A basic Task Management app, with Tasks and People who can be assigned to
+ tasks. Great for learning how to use Keystone. It’s also a starter for other
+ feature projects.
diff --git a/docs/content/examples/testing.yaml b/docs/content/examples/testing.yaml
new file mode 100644
index 00000000000..fd7ac3a82d3
--- /dev/null
+++ b/docs/content/examples/testing.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/testing
+title: Testing
+kind: standalone
+description: >
+ Shows you how to write tests against the GraphQL API to your Keystone system.
diff --git a/docs/content/examples/virtual-field.yaml b/docs/content/examples/virtual-field.yaml
new file mode 100644
index 00000000000..2ceeffce844
--- /dev/null
+++ b/docs/content/examples/virtual-field.yaml
@@ -0,0 +1,5 @@
+url: https://github.com/keystonejs/keystone/tree/main/examples/virtual-field
+title: Virtual fields
+kind: standalone
+description: >
+ Implements virtual fields in a Keystone list.
diff --git a/docs/content/featured-docs.yaml b/docs/content/featured-docs.yaml
new file mode 100644
index 00000000000..533b6539c7b
--- /dev/null
+++ b/docs/content/featured-docs.yaml
@@ -0,0 +1,145 @@
+groups:
+ - groupName: Walkthroughs
+ groupDescription: |
+ Step-by-step instructions for getting things done with Keystone.
+ gradient: grad2
+ items:
+ - label: Keystone Quick Start
+ link:
+ discriminant: docs
+ value:
+ docPage: getting-started
+ description: |
+ Take a tour of Keystone in minutes with our CLI starter project
+ - label: 'Lesson 1: Installing Keystone'
+ link:
+ discriminant: docs
+ value:
+ docPage: walkthroughs/lesson-1
+ description: |
+ Get Keystone up and running with your first content type
+ - label: 'Lesson 2: Relating things'
+ link:
+ discriminant: docs
+ value:
+ docPage: walkthroughs/lesson-2
+ description: |
+ Connect two content types and learn how to configure the appearance of field inputs
+
+ - label: 'Lesson 3: Publishing Workflows'
+ link:
+ discriminant: docs
+ value:
+ docPage: walkthroughs/lesson-3
+ description: |
+ Support publishing needs with Keystone's `select` and `timestamp` fields
+
+ - label: 'Lesson 4: Auth & Sessions'
+ link:
+ discriminant: docs
+ value:
+ docPage: walkthroughs/lesson-4
+ description: |
+ Add sessions, password protection, and a sign-in screen to your Keystone app
+ - label: 'Lesson 5: Rich Text'
+ link:
+ discriminant: docs
+ value:
+ docPage: walkthroughs/lesson-5
+ description: |
+ Add a powerful `document` field to your app and learn how to configure it to meet your needs
+ - groupName: Guides
+ groupDescription: >
+ Practical explanations of Keystone’s fundamental building blocks. When
+ you’re trying to get something done, Keystone guides show you how to think
+ about, and get the most out of each feature.
+ gradient: grad2
+ items:
+ - label: Command Line Foundations
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/cli
+ description: |
+ Keystone’s CLI helps you develop, build, and deploy projects. This guide explains all you need to standup a new backend in the terminal.
+
+ - label: Understanding Relationships
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/relationships
+ description: |
+ Learn how to reason about and configure relationships in Keystone, so you can bring value to your project through structured content.
+
+ - label: GraphQL Queries - Filters
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/filters
+ description: |
+ Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will show you how to use filters to get the data you need from your system.
+
+ - label: Understanding Hooks
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/hooks
+ description: |
+ Learn how to use Hooks within your schema to extend Keystone’s powerful CRUD GraphQL APIs with your own business logic.
+
+ - label: How To Use Document Fields
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/document-fields
+ description: |
+ Keystone’s document field is a highly customisable rich text editor that stores content as structured JSON. Learn how to configure it and incorporate your own custom React components.
+
+ - label: Document Field Demo
+ link:
+ discriminant: url
+ value:
+ url: /docs/guides/document-field-demo
+ description: |
+ Test drive the many features of Keystone’s Document field on this website.
+
+ - label: Custom Fields
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/custom-fields
+ description: |
+ Learn how to define your own custom field types in Keystone, with customisable backend data structure, and Admin UI appearance.
+
+ - label: Testing Guide
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/testing
+ description: |
+ Learn how to test the behaviour of your Keystone system to ensure it does what you expect.
+
+ - label: Virtual Fields
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/virtual-fields
+ description: |
+ Virtual fields offer a powerful way to extend your GraphQL API. This guide introduces the syntax and shows you how start simply and end up with a complex result.
+
+ - label: Choosing a Database
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/choosing-a-database
+ description: |
+ Keystone supports Postgres, MySQL and SQLite. This guide explains how to choose the best for your project.
+
+ - label: Images and Files
+ link:
+ discriminant: docs
+ value:
+ docPage: guides/images-and-files
+ description: |
+ Learn how to store and manage Images and Files in Keystone.
+
diff --git a/docs/content/featured-examples.yaml b/docs/content/featured-examples.yaml
new file mode 100644
index 00000000000..b980a9de66e
--- /dev/null
+++ b/docs/content/featured-examples.yaml
@@ -0,0 +1,23 @@
+label: Example Projects
+description: >
+ A growing collection of projects you can run locally to learn more about
+ Keystone features. Use these as a reference for best practice, and a jumping
+ off point when adding features to your own Keystone project. [View on Github
+ →](https://github.com/keystonejs/keystone/tree/main/examples)
+gradient: grad3
+items:
+ - blog
+ - task-manager
+ - extend-graphql-schema
+ - default-values
+ - virtual-field
+ - document-field
+ - testing
+ - auth
+ - json-field
+ - custom-field-view
+ - custom-field-type
+ - custom-admin-ui-pages
+ - custom-admin-ui-logo
+ - custom-admin-ui-navigation
+ - document-field-customisation
diff --git a/docs/content/navigation.yaml b/docs/content/navigation.yaml
new file mode 100644
index 00000000000..3d18e22fce1
--- /dev/null
+++ b/docs/content/navigation.yaml
@@ -0,0 +1,285 @@
+navGroups:
+ - groupName: Start
+ items:
+ - label: Docs Home
+ link:
+ discriminant: url
+ value: /docs
+ status: default
+ - label: Walkthroughs
+ link:
+ discriminant: url
+ value: /docs/walkthroughs
+ status: default
+ - label: Examples
+ link:
+ discriminant: url
+ value: /docs/examples
+ status: default
+ - groupName: Guides
+ items:
+ - label: Overview
+ link:
+ discriminant: url
+ value: /docs/guides/overview
+ status: default
+ - label: Command Line
+ link:
+ discriminant: page
+ value: guides/cli
+ status: default
+ - label: Relationships
+ link:
+ discriminant: page
+ value: guides/relationships
+ status: default
+ - label: Choosing a Database
+ link:
+ discriminant: page
+ value: guides/choosing-a-database
+ status: default
+ - label: Database Migration
+ link:
+ discriminant: page
+ value: guides/database-migration
+ status: new
+ - label: Query Filters
+ link:
+ discriminant: page
+ value: guides/filters
+ status: updated
+ - label: Hooks
+ link:
+ discriminant: page
+ value: guides/hooks
+ status: updated
+ - label: Auth & Access Control
+ link:
+ discriminant: page
+ value: guides/auth-and-access-control
+ status: new
+ - label: Images & Files
+ link:
+ discriminant: page
+ value: guides/images-and-files
+ status: new
+ - label: GraphQL Schema Extension
+ link:
+ discriminant: page
+ value: guides/schema-extension
+ status: new
+ - label: Testing
+ link:
+ discriminant: page
+ value: guides/testing
+ status: default
+ - label: Document Fields
+ link:
+ discriminant: page
+ value: guides/document-fields
+ status: default
+ - label: Document Fields Demo
+ link:
+ discriminant: url
+ value: /docs/guides/document-field-demo
+ status: default
+ - label: Virtual Fields
+ link:
+ discriminant: page
+ value: guides/virtual-fields
+ status: default
+ - label: Custom Fields
+ link:
+ discriminant: page
+ value: guides/custom-fields
+ status: default
+ - label: Custom Admin UI Logo
+ link:
+ discriminant: page
+ value: guides/custom-admin-ui-logo
+ status: default
+ - label: Custom Admin UI Pages
+ link:
+ discriminant: page
+ value: guides/custom-admin-ui-pages
+ status: default
+ - label: Custom Admin UI Navigation
+ link:
+ discriminant: page
+ value: guides/custom-admin-ui-navigation
+ status: default
+ - groupName: Configuration
+ items:
+ - label: Overview
+ link:
+ discriminant: url
+ value: /docs/config/overview
+ status: default
+ - label: Config
+ link:
+ discriminant: url
+ value: /docs/config/config
+ status: default
+ - label: Lists
+ link:
+ discriminant: page
+ value: config/lists
+ status: default
+ - label: Authentication
+ link:
+ discriminant: page
+ value: config/auth
+ status: default
+ - label: Access Control
+ link:
+ discriminant: page
+ value: config/access-control
+ status: updated
+ - label: Hooks
+ link:
+ discriminant: page
+ value: config/hooks
+ status: updated
+ - label: Session
+ link:
+ discriminant: page
+ value: config/session
+ status: default
+ - groupName: Fields
+ items:
+ - label: Overview
+ link:
+ discriminant: page
+ value: fields/overview
+ status: default
+ - label: BigInt
+ link:
+ discriminant: page
+ value: fields/bigint
+ status: default
+ - label: Calendar Day
+ link:
+ discriminant: page
+ value: fields/calendarday
+ status: default
+ - label: Checkbox
+ link:
+ discriminant: page
+ value: fields/checkbox
+ status: default
+ - label: Cloudinary Image
+ link:
+ discriminant: page
+ value: fields/cloudinaryimage
+ status: default
+ - label: Decimal
+ link:
+ discriminant: page
+ value: fields/decimal
+ status: default
+ - label: Document
+ link:
+ discriminant: page
+ value: fields/document
+ status: default
+ - label: File
+ link:
+ discriminant: page
+ value: fields/file
+ status: default
+ - label: Float
+ link:
+ discriminant: page
+ value: fields/float
+ status: default
+ - label: Image
+ link:
+ discriminant: page
+ value: fields/image
+ status: default
+ - label: Integer
+ link:
+ discriminant: page
+ value: fields/integer
+ status: default
+ - label: JSON
+ link:
+ discriminant: page
+ value: fields/json
+ status: default
+ - label: Multiselect
+ link:
+ discriminant: page
+ value: fields/multiselect
+ status: default
+ - label: Password
+ link:
+ discriminant: page
+ value: fields/password
+ status: default
+ - label: Relationship
+ link:
+ discriminant: page
+ value: fields/relationship
+ status: default
+ - label: Select
+ link:
+ discriminant: page
+ value: fields/select
+ status: default
+ - label: Text
+ link:
+ discriminant: page
+ value: fields/text
+ status: default
+ - label: Timestamp
+ link:
+ discriminant: page
+ value: fields/timestamp
+ status: default
+ - label: Virtual
+ link:
+ discriminant: page
+ value: fields/virtual
+ status: default
+ - groupName: Context
+ items:
+ - label: Overview
+ link:
+ discriminant: page
+ value: context/overview
+ status: default
+ - label: getContext
+ link:
+ discriminant: page
+ value: context/get-context
+ status: default
+ - label: Query
+ link:
+ discriminant: page
+ value: context/query
+ status: default
+ - label: DB
+ link:
+ discriminant: page
+ value: context/db-items
+ status: default
+ - groupName: GraphQL
+ items:
+ - label: Overview
+ link:
+ discriminant: page
+ value: graphql/overview
+ status: updated
+ - label: Query Filters
+ link:
+ discriminant: page
+ value: graphql/filters
+ status: updated
+ - groupName: Reference
+ items:
+ - label: Telemetry
+ link:
+ discriminant: page
+ value: reference/telemetry
+ status: default
\ No newline at end of file
diff --git a/docs/keystatic.config.tsx b/docs/keystatic.config.tsx
index 2881a47530f..f1c9605268b 100644
--- a/docs/keystatic.config.tsx
+++ b/docs/keystatic.config.tsx
@@ -1,9 +1,8 @@
-// keystatic.config.ts
-import { config, fields, collection } from '@keystatic/core'
+import { config, fields, collection, singleton } from '@keystatic/core'
import { superscriptIcon } from '@keystar/ui/icon/icons/superscriptIcon'
-
import { inline, mark, wrapper } from '@keystatic/core/content-components'
-// import { WellPreview } from './keystatic/admin-previews'
+
+import { gradientSelector } from './keystatic/gradient-selector'
export default config({
storage: {
@@ -13,17 +12,25 @@ export default config({
brand: {
name: 'Keystone Website',
},
+ navigation: {
+ Pages: ['docs', 'posts', 'examples'],
+ Config: ['navigation', 'featuredDocs', 'featuredExamples'],
+ },
},
collections: {
+ // ------------------------------
+ // Docs
+ // ------------------------------
docs: collection({
- label: 'Docs',
+ label: 'Docs Pages',
path: 'content/docs/**',
slugField: 'title',
columns: ['title'],
format: { contentField: 'content' },
+ entryLayout: 'content',
schema: {
- title: fields.slug({ name: { label: 'Title' } }),
- description: fields.text({ label: 'Description' }),
+ title: fields.slug({ name: { label: 'Title', validation: { isRequired: true } } }),
+ description: fields.text({ label: 'Description', validation: { isRequired: true } }),
content: fields.markdoc({
label: 'Content',
extension: 'md',
@@ -98,10 +105,12 @@ export default config({
},
}),
+ // ------------------------------
// Blog
+ // ------------------------------
posts: collection({
+ label: 'Blog Posts',
path: 'content/blog/*',
- label: 'Blog',
slugField: 'title',
columns: ['title'],
format: { contentField: 'content' },
@@ -115,5 +124,163 @@ export default config({
content: fields.markdoc({ label: 'Content', extension: 'md' }),
},
}),
+
+ // ------------------------------
+ // Examples
+ // ------------------------------
+ examples: collection({
+ label: 'GitHub Examples',
+ path: 'content/examples/*',
+ slugField: 'title',
+ schema: {
+ title: fields.slug({ name: { label: 'Title' } }),
+ kind: fields.select({
+ label: 'Example Kind',
+ options: [
+ { label: 'Standalone', value: 'standalone' },
+ { label: 'End-to-End', value: 'end-to-end' },
+ { label: 'Deployment', value: 'deployment' },
+ ],
+ defaultValue: 'standalone',
+ }),
+ url: fields.text({
+ label: 'URL',
+ validation: { isRequired: true },
+ }),
+ description: fields.markdoc.inline({ label: 'Description' }),
+ },
+ }),
+ },
+ singletons: {
+ // ------------------------------
+ // Navigation
+ // ------------------------------
+ navigation: singleton({
+ label: 'Docs Sidebar Navigation',
+ path: 'content/navigation',
+ schema: {
+ navGroups: fields.array(
+ fields.object({
+ groupName: fields.text({ label: 'Group name' }),
+ items: fields.array(
+ fields.object({
+ label: fields.text({
+ label: 'Label',
+ description: "Required when using a URL, or overriding the page's title",
+ }),
+ link: fields.conditional(
+ fields.select({
+ label: 'Link type',
+ options: [
+ { label: 'Page', value: 'page' },
+ { label: 'URL', value: 'url' },
+ ],
+ defaultValue: 'page',
+ }),
+ {
+ page: fields.relationship({
+ label: 'Docs Page',
+ collection: 'docs',
+ }),
+ url: fields.text({ label: 'URL' }),
+ }
+ ),
+ status: fields.select({
+ label: 'Status',
+ options: [
+ { label: 'Default', value: 'default' },
+ { label: 'New', value: 'new' },
+ { label: 'Updated', value: 'updated' },
+ ],
+ defaultValue: 'default',
+ }),
+ }),
+ {
+ label: 'Navigation items',
+ itemLabel: (props) => props.fields.label.value,
+ }
+ ),
+ }),
+ {
+ label: 'Navigation groups',
+ itemLabel: (props) => props.fields.groupName.value,
+ }
+ ),
+ },
+ }),
+
+ // ------------------------------
+ // Featured Docs
+ // ------------------------------
+ featuredDocs: singleton({
+ label: 'Featured Docs',
+ path: 'content/featured-docs',
+ schema: {
+ groups: fields.array(
+ fields.object({
+ groupName: fields.text({ label: 'Group name' }),
+ groupDescription: fields.markdoc.inline({ label: 'Group description' }),
+ gradient: gradientSelector({ defaultValue: 'grad1' }),
+ items: fields.array(
+ fields.object({
+ label: fields.text({
+ label: 'Label',
+ description: "Required when using a URL, or overriding the page's title",
+ validation: { isRequired: true },
+ }),
+ link: fields.conditional(
+ fields.select({
+ label: 'Link type',
+ options: [
+ { label: 'Docs Page', value: 'docs' },
+ { label: 'URL', value: 'url' },
+ ],
+ defaultValue: 'docs',
+ }),
+ {
+ docs: fields.object(
+ {
+ docPage: fields.relationship({
+ label: 'Docs Page',
+ collection: 'docs',
+ }),
+ description: fields.markdoc.inline({ label: 'Description' }),
+ },
+ {
+ label: 'Docs Page',
+ }
+ ),
+ url: fields.object({
+ url: fields.text({ label: 'URL' }),
+ description: fields.markdoc.inline({ label: 'Description' }),
+ }),
+ }
+ ),
+ }),
+ {
+ label: 'Featured Items',
+ itemLabel: (props) =>
+ `${props.fields.label.value} — [${props.fields.link.discriminant}]`,
+ }
+ ),
+ }),
+ {
+ label: 'Featured Groups',
+ itemLabel: (props) => props.fields.groupName.value,
+ }
+ ),
+ },
+ }),
+
+ featuredExamples: singleton({
+ label: 'Featured Examples',
+ path: 'content/featured-examples',
+ schema: {
+ label: fields.text({ label: 'Label' }),
+ description: fields.markdoc.inline({ label: 'Group description' }),
+ gradient: gradientSelector({ defaultValue: 'grad1' }),
+ items: fields.multiRelationship({ label: 'Examples List', collection: 'examples' }),
+ },
+ }),
},
})
diff --git a/docs/keystatic/admin-previews.tsx b/docs/keystatic/admin-previews.tsx
deleted file mode 100644
index 87547995376..00000000000
--- a/docs/keystatic/admin-previews.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-'use client'
-
-import { Well } from '../components/primitives/Well'
-
-export function WellPreview (data) {
- return {data.children}
-}
diff --git a/docs/keystatic/get-featured-docs-map.ts b/docs/keystatic/get-featured-docs-map.ts
new file mode 100644
index 00000000000..6af662e1fc5
--- /dev/null
+++ b/docs/keystatic/get-featured-docs-map.ts
@@ -0,0 +1,56 @@
+import { type Tag, transform } from '@markdoc/markdoc'
+import { reader } from './reader'
+import { baseMarkdocConfig } from '../markdoc/config'
+
+export type FeaturedDocsMap = Awaited>
+
+export async function getFeaturedDocsMap () {
+ const featuredDocs = await reader.singletons.featuredDocs.read({ resolveLinkedFiles: true })
+ if (!featuredDocs) return null
+
+ // We need the docs data as well...
+ const docs = await reader.collections.docs.all()
+ // Individual doc accessor
+ const docsBySlug = new Map(docs.map((doc) => [doc.slug, doc]))
+
+ // Each `item` will need to be processed differently based on the `link` discriminant
+ async function processItem ({ label, link, wide, gradient }) {
+ const { discriminant, value } = link
+ let description: Tag | null = null
+ let href: string = '#'
+
+ switch (discriminant) {
+ case 'url': {
+ description = transform(value.description.node, baseMarkdocConfig) as Tag
+ href = value.url
+ break
+ }
+ case 'docs': {
+ const docPage = value.docPage ? docsBySlug.get(value.docPage) : null
+ if (!docPage) throw new Error(`No doc page found for slug: ${value.docPage}`)
+ description = transform(value.description.node, baseMarkdocConfig) as Tag
+ href = docPage.slug ? `/docs/${docPage.slug}` : '#'
+ break
+ }
+ }
+
+ return { label, description, href, wide, gradient }
+ }
+
+ // Processing all groups...
+ const processedGroups = await Promise.all(
+ featuredDocs.groups.map(async ({ groupName, groupDescription, gradient, items }) => {
+ const transformedGroupDescription = transform(groupDescription.node, baseMarkdocConfig) as Tag
+ const processedItems = await Promise.all(items.map(processItem))
+
+ return {
+ groupName,
+ groupDescription: transformedGroupDescription,
+ gradient,
+ items: processedItems,
+ }
+ })
+ )
+
+ return processedGroups
+}
\ No newline at end of file
diff --git a/docs/keystatic/gradient-selector.ts b/docs/keystatic/gradient-selector.ts
new file mode 100644
index 00000000000..75da45021f8
--- /dev/null
+++ b/docs/keystatic/gradient-selector.ts
@@ -0,0 +1,17 @@
+import { fields } from '@keystatic/core'
+
+export type Gradient = 'grad1' | 'grad2' | 'grad3' | 'grad4'
+
+export function gradientSelector ({ defaultValue = 'grad1' }: { defaultValue: Gradient }) {
+ return fields.select({
+ label: 'Gradient',
+ description: 'The gradient to use for the group',
+ options: [
+ { label: '1', value: 'grad1' },
+ { label: '2', value: 'grad2' },
+ { label: '3', value: 'grad3' },
+ { label: '4', value: 'grad4' },
+ ],
+ defaultValue: defaultValue,
+ })
+}
diff --git a/docs/lib/keystatic-reader.ts b/docs/keystatic/reader.ts
similarity index 100%
rename from docs/lib/keystatic-reader.ts
rename to docs/keystatic/reader.ts
diff --git a/docs/markdoc/headings.ts b/docs/markdoc/headings.ts
new file mode 100644
index 00000000000..273bfc2bdd6
--- /dev/null
+++ b/docs/markdoc/headings.ts
@@ -0,0 +1,38 @@
+
+
+
+import type { RenderableTreeNode, Tag } from '@markdoc/markdoc'
+import { isTag } from './isTag'
+
+export type HeadingType = {
+ id: string
+ depth: number
+ label: string
+}
+
+export function extractHeadings (content: Tag): HeadingType[] {
+ const headings: HeadingType[] = []
+ for (const child of content.children) {
+ if (isTag(child) && child.name === 'Heading') {
+ headings.push({
+ id: child.attributes.id,
+ depth: child.attributes.level,
+ label: stringifyDocContent(child),
+ })
+ }
+ }
+ return headings
+}
+
+function stringifyDocContent (node: RenderableTreeNode): string {
+ if (typeof node === 'string') {
+ return node
+ }
+ if (Array.isArray(node)) {
+ return node.map(stringifyDocContent).join('')
+ }
+ if (!isTag(node)) {
+ return ''
+ }
+ return node.children.map(stringifyDocContent).join('')
+}
diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts
index fd36f9494e2..4f11a03dc6c 100644
--- a/docs/next-env.d.ts
+++ b/docs/next-env.d.ts
@@ -1,6 +1,5 @@
///
///
-///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/docs/package.json b/docs/package.json
index d726501b9de..9d48bc9b2d4 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -16,11 +16,11 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@codemod/core": "^2.0.1",
- "@emotion/cache": "11.11.0",
+ "@emotion/cache": "11.13.1",
"@emotion/css": "^11.7.1",
"@emotion/react": "^11.7.1",
"@emotion/server": "11.11.0",
- "@emotion/weak-memoize": "^0.3.0",
+ "@emotion/weak-memoize": "^0.4.0",
"@keystar/ui": "^0.7.6",
"@keystatic/core": "^0.5.24",
"@keystatic/next": "^5.0.1",
@@ -39,26 +39,26 @@
"globby": "^14.0.0",
"js-yaml": "^4.1.0",
"lodash.debounce": "^4.0.8",
- "next": "catalog:",
+ "next": "^14.2.0",
"next-compose-plugins": "^2.2.1",
"prism-react-renderer": "^2.0.0",
- "react": "catalog:",
- "react-dom": "catalog:",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"react-focus-lock": "^2.7.1",
"rss": "^1.2.2",
- "tsx": "catalog:"
+ "tsx": "^4.0.0"
},
"devDependencies": {
"@types/babel__core": "7.20.5",
"@types/facepaint": "^1.2.2",
"@types/gtag.js": "^0.0.20",
"@types/js-yaml": "^4.0.5",
- "@types/react": "catalog:",
- "@types/react-dom": "catalog:",
"@types/lodash.debounce": "^4.0.6",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
"@types/rss": "^0.0.32",
"next-sitemap": "^4.0.0",
- "typescript": "catalog:"
+ "typescript": "^5.5.0"
},
"repository": "https://github.com/keystonejs/keystone/tree/main/docs"
}
diff --git a/docs/pages/_app.tsx b/docs/pages/_app.tsx
deleted file mode 100644
index 306643d3c92..00000000000
--- a/docs/pages/_app.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx, Global, css, CacheProvider } from '@emotion/react'
-import type { AppProps } from 'next/app'
-import { cache } from '@emotion/css'
-
-import Head from 'next/head'
-import { proseStyles } from '../lib/prose-lite'
-import { Theme } from '../components/Theme'
-import { NavContextProvider } from '../components/docs/Navigation'
-import { SkipLinks } from '../components/SkipLinks'
-
-export default function App ({ Component, pageProps }: AppProps) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/docs/pages/_document.tsx b/docs/pages/_document.tsx
deleted file mode 100644
index c2117349b24..00000000000
--- a/docs/pages/_document.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import Document, { Html, Head, Main, NextScript, type DocumentContext } from 'next/document'
-import React from 'react'
-import createEmotionServer from '@emotion/server/create-instance'
-import { cache } from '@emotion/css'
-
-const { extractCriticalToChunks } = createEmotionServer(cache)
-
-class MyDocument extends Document {
- static async getInitialProps (ctx: DocumentContext) {
- const initialProps = await Document.getInitialProps(ctx)
- const data = extractCriticalToChunks(initialProps.html)
-
- return {
- ...initialProps,
- styles: [
-
- {initialProps.styles}
- {data!.styles.map((data, i) => (
-
- ))}
- ,
- ],
- }
- }
-
- render () {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
- The page is server rendered and hydrated on the browser client.
- While server rendering we do not know what the user's preferred/saved theme is and default to light theme.
- So we run this script in the browser before the page is rendered (not the react render but the browser render)
- and set the theme class in html element so our styles would know which theme to paint on first paint.
- All this to avoid a flash of default light theme when user either prefers dark theme or has previously
- saved dark theme to their local storage. ¯\_(ツ)_/¯ React is hard sometimes.
- */}
-
-
-
-
-
-
-
- )
- }
-}
-
-export default MyDocument
diff --git a/docs/pages/blog/[post].tsx b/docs/pages/blog/[post].tsx
deleted file mode 100644
index e5001be227a..00000000000
--- a/docs/pages/blog/[post].tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-import { transform } from '@markdoc/markdoc'
-import {
- type GetStaticPathsResult,
- type GetStaticPropsContext,
- type GetStaticPropsResult,
- type InferGetStaticPropsType,
-} from 'next'
-import Link from 'next/link'
-import { useRouter } from 'next/router'
-import { parse, format } from 'date-fns'
-import { type BlogContent } from '../../markdoc'
-import { extractHeadings, Markdoc } from '../../components/Markdoc'
-import { BlogPage } from '../../components/Page'
-import { Heading } from '../../components/docs/Heading'
-import { Type } from '../../components/primitives/Type'
-import { getOgAbsoluteUrl } from '../../lib/og-util'
-import { reader } from '../../lib/keystatic-reader'
-import { baseMarkdocConfig } from '../../markdoc/config'
-
-export default function Page (props: InferGetStaticPropsType) {
- const router = useRouter()
- const headings = [
- { id: 'title', depth: 1, label: props.title },
- ...extractHeadings(props.content),
- ]
-
- const publishedDate = props.publishDate
- const parsedDate = parse(publishedDate, 'yyyy-M-d', new Date())
- const formattedDateStr = format(parsedDate, 'MMMM do, yyyy')
-
- let ogImageUrl = props.metaImageUrl
- if (!ogImageUrl) {
- ogImageUrl = getOgAbsoluteUrl({
- title: props.title,
- type: 'Blog',
- })
- }
-
- return (
-
-
- {props.title}
-
-
-
- Published on {formattedDateStr}
- {props.authorHandle ? (
-
- {' '}
- by{' '}
-
- {props.authorName}
-
-
- ) : (
- by {props.authorName}
- )}
-
-
- {props.content.children.map((child, i) => (
-
- ))}
-
- )
-}
-
-export async function getStaticPaths (): Promise {
- const posts = await reader.collections.posts.list()
- return {
- paths: posts.map(post => ({ params: { post } })),
- fallback: false,
- }
-}
-
-type KeystaticPostsContent = Omit & {
- authorHandle: string | null
- metaImageUrl: string | null
-}
-
-export async function getStaticProps (
- args: GetStaticPropsContext<{ post: string }>
-): Promise> {
- const keystaticPost = await reader.collections.posts.read(args.params!.post, {
- resolveLinkedFiles: true,
- })
-
- if (!keystaticPost) throw new Error(`Post not found: ${args.params!.post}`)
-
- const transformedContent = transform(keystaticPost.content.node, baseMarkdocConfig)
-
- return { props: { ...keystaticPost, content: JSON.parse(JSON.stringify(transformedContent)) } }
-}
diff --git a/docs/pages/docs/[...rest].tsx b/docs/pages/docs/[...rest].tsx
deleted file mode 100644
index 63733032634..00000000000
--- a/docs/pages/docs/[...rest].tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react'
-import {
- type GetStaticPathsResult,
- type GetStaticPropsContext,
- type GetStaticPropsResult,
- type InferGetStaticPropsType,
-} from 'next'
-import { useRouter } from 'next/router'
-import { type DocsContent } from '../../markdoc'
-import { extractHeadings, Markdoc } from '../../components/Markdoc'
-import { DocsPage } from '../../components/Page'
-import { Heading } from '../../components/docs/Heading'
-import { reader } from '../../lib/keystatic-reader'
-import { transform } from '@markdoc/markdoc'
-import { baseMarkdocConfig } from '../../markdoc/config'
-
-export default function DocPage (props: InferGetStaticPropsType) {
- const router = useRouter()
- const headings = [
- { id: 'title', depth: 1, label: props.title },
- ...extractHeadings(props.content),
- ]
- return (
-
-
- {props.title}
-
- {props.content.children.map((child, i) => (
-
- ))}
-
- )
-}
-
-export async function getStaticPaths (): Promise {
- const pages = await reader.collections.docs.list()
- return {
- paths: pages.map(page => ({ params: { rest: page.split('/') } })),
- fallback: false,
- }
-}
-
-export async function getStaticProps (
- args: GetStaticPropsContext<{ rest: string[] }>
-): Promise> {
- const doc = await reader.collections.docs.read(args.params!.rest.join('/'), {
- resolveLinkedFiles: true,
- })
-
- if (!doc) throw new Error(`Doc page not found: ${args.params!.rest.join('/')}`)
-
- const transformedContent = transform(doc.content.node, baseMarkdocConfig)
-
- return { props: { ...doc, content: JSON.parse(JSON.stringify(transformedContent)) } }
-}
diff --git a/docs/pages/docs/examples.tsx b/docs/pages/docs/examples.tsx
deleted file mode 100644
index e4b551c3f79..00000000000
--- a/docs/pages/docs/examples.tsx
+++ /dev/null
@@ -1,316 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
-import { GitHubExamplesCTA } from '../../components/docs/GitHubExamplesCTA'
-import { Type } from '../../components/primitives/Type'
-import { DocsPage } from '../../components/Page'
-import { Well } from '../../components/primitives/Well'
-import { useMediaQuery } from '../../lib/media'
-import { InlineCode } from '../../components/primitives/Code'
-
-export default function Docs () {
- const mq = useMediaQuery()
-
- return (
-
-
- Examples
-
-
-
- A growing collection of projects you can run locally to learn more about Keystone’s
- capabilities. Each example includes documentation explaining the how and why. Use them as a
- reference for best practice, and a jumping off point when adding features to your own
- Keystone project.
-
-
-
-
-
- Standalone Examples
-
-
-
- Standalone examples demonstrate how a particular feature works or how to solve a problem
- with Keystone.
-
-
-
-
- A basic Blog schema with Posts and Authors. Use this as a starting place for learning how
- to use Keystone.
-
-
- A basic Task Management app, with Tasks and People who can be assigned to tasks. Great for
- learning how to use Keystone.
-
-
-
- Adds password-based authentication to the Task Manager starter project.
-
-
- Adds a custom logo component in the Admin UI.
-
-
- Adds a custom Navigation component to the Admin UI.
-
-
- Adds a custom page in the Admin UI.
-
-
- Adds a custom field type based on the integer field type which
- lets users rate items on a 5-star scale.
-
-
- Adds a custom Admin UI view to a json field which provides a
- customised editing experience for users.
-
-
- Demonstrates how to use default values for fields.
-
-
- Illustrates how to configure document fields in your Keystone
- system and render their data in a frontend application.
-
-
- Shows you how to extend the Keystone GraphQL API with custom queries and mutations.
-
-
- Demonstrates how to extend the GraphQL API provided by Keystone with custom queries and
- mutations using graphql-ts.
-
-
- Shows you how to use Nexus to extend the GraphQL API provided by Keystone
- with custom queries and mutations.
-
-
-
- Illustrates how to use the json field type. Builds on the Task
- Manager starter project.
-
-
-
- Demonstrates how to create REST endpoints by extending Keystone's express app and using
- the Query API to execute queries against the schema.
-
-
- Shows you how to write tests against the GraphQL API to your Keystone system.
-
-
- Implements virtual fields in a Keystone list.
-
-
- Demonstrates how to use singleton lists with Keystone.
-
-
-
-
- End-to-End Examples
-
-
-
- End to end examples demonstrate how a feature works or how to solve a problem with an
- independent frontend application and a Keystone server.
-
-
-
-
- Example to demonstrate customisation of Keystone's document field and document renderer.
-
-
-
- Shows you how to use Keystone as a data engine within Next.js applications.
-
-
-
-
- Deployment Examples
-
-
-
- Examples with all the code and documentation you need to get a Keystone project hosted on
- the web. You can find them in the{' '}
- Keystone Github Organisation .
-
-
-
-
- Based on the with-auth project, this example deploys a simple app
- backend to Heroku. Includes a one-click deployment for Heroku account
- holders.
-
-
- Deploys a Keystone app backend to Microsoft Azure. Based on the{' '}
- with-auth project. One-click deployment {' '}
- included.
-
-
- Deploys a Keystone app backend to Railway. Based on the with-auth {' '}
- project. One-click deployment included.
-
-
-
- )
-}
diff --git a/docs/pages/docs/index.tsx b/docs/pages/docs/index.tsx
deleted file mode 100644
index 880a7280f0e..00000000000
--- a/docs/pages/docs/index.tsx
+++ /dev/null
@@ -1,344 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-import Link from 'next/link'
-
-import { CommunitySlackCTA } from '../../components/docs/CommunitySlackCTA'
-import { Keystone5DocsCTA } from '../../components/docs/Keystone5DocsCTA'
-import { Examples } from '../../components/docs/ExamplesList'
-import { Walkthroughs } from '../../components/docs/WalkthroughsList'
-import { Type } from '../../components/primitives/Type'
-import { Well } from '../../components/primitives/Well'
-import { DocsPage } from '../../components/Page'
-import { useMediaQuery } from '../../lib/media'
-import { CommunityCta } from '../../components/content/CommunityCta'
-import { Content } from '../../components/icons/Content'
-import { Code } from '../../components/icons/Code'
-import { Bulb } from '../../components/icons/Bulb'
-import { Video } from '../../components/icons/Video'
-import { Organization } from '../../components/icons/Organization'
-import { Alert } from '../../components/primitives/Alert'
-import { Button } from '../../components/primitives/Button'
-import { ArrowR } from '../../components/icons'
-
-export default function Docs () {
- const mq = useMediaQuery()
-
- return (
-
-
- Developer Docs
-
-
-
-
-
-
- Looking for enterprise-grade consulting & support?
-
-
- Learn more
-
-
-
-
- The Keystone Experience
-
-
-
- Discover the vision behind Keystone and what it's like to work with. If you’ve just heard
- of Keystone, start here first:
-
-
a': {
- borderRadius: '1rem',
- boxShadow: '0 0 5px var(--shadow)',
- padding: '1.5rem',
- color: 'var(--app-bg)',
- transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease',
- textDecoration: 'none !important',
- '&:hover, &:focus': {
- boxShadow: '0 7px 21px var(--shadow)',
- transform: 'translateY(-4px)',
- },
- '& svg': {
- height: '2rem',
- },
- },
- })}
- >
-
-
-
- Video Intro →
-
-
- Learn how Keystone’s leading a new generation of content management tools.
-
-
-
-
-
- Why Keystone →
-
-
- The makers. The vision. What’s in the box, and what you can build with it.
-
-
-
-
- a': {
- borderRadius: '1rem',
- boxShadow: '0 0 5px var(--shadow)',
- padding: '1.5rem',
- color: 'var(--app-bg)',
- transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease',
- textDecoration: 'none !important',
- '&:hover, &:focus': {
- boxShadow: '0 7px 21px var(--shadow)',
- transform: 'translateY(-4px)',
- },
- '& svg': {
- height: '2rem',
- },
- },
- })}
- >
-
-
-
- For Developers →
-
-
- Built the way you’d want it made. Keystone fits with the tools you know and love.
-
-
-
-
-
- For Editors →
-
-
- The configurable editing environment you need to do your best work.
-
-
-
-
-
- For Organisations →
-
-
- Own your data. Start fast. Find your audience anywhere. Scale on your terms.
-
-
-
-
-
- Walkthroughs
-
-
-
- Step-by-step instructions for getting things done with Keystone.
-
-
-
-
-
- Guides
-
-
-
- Practical explanations of Keystone’s fundamental building blocks. When you’re trying to get
- something done, Keystone guides show you how to think about, and get the most out of each
- feature.
-
-
-
-
- Keystone’s CLI helps you develop, build, and deploy projects. This guide explains all you
- need to standup a new backend in the terminal.
-
-
- Learn how to reason about and configure relationships in Keystone, so you can bring value
- to your project through structured content.
-
-
- Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will
- show you how to use filters to get the data you need from your system.
-
-
- Learn how to use Hooks within your schema to extend Keystone’s powerful CRUD GraphQL APIs
- with your own business logic.
-
-
- Keystone’s document field is a highly customisable rich text editor that stores content as
- structured JSON. Learn how to configure it and incorporate your own custom React
- components.
-
-
- Test drive the many features of Keystone’s Document field on this website.
-
-
- Learn how to define your own custom field types in Keystone, with customisable backend
- data structure, and Admin UI appearance.
-
-
- Learn how to test the behaviour of your Keystone system to ensure it does what you expect.
-
-
- Virtual fields offer a powerful way to extend your GraphQL API. This guide introduces the
- syntax and shows you how start simply and end up with a complex result.
-
-
- Keystone supports Postgres, MySQL and SQLite. This guide explains how to choose the best
- for your project.
-
-
- Learn how to store and manage Images and Files in Keystone.
-
-
-
-
- Example projects
-
-
-
- A growing collection of projects you can run locally to learn more about Keystone features.
- Use these as a reference for best practice, and a jumping off point when adding features to
- your own Keystone project.{' '}
-
- View on Github →
-
-
-
-
-
-
-
- )
-}
diff --git a/docs/pages/docs/walkthroughs/index.tsx b/docs/pages/docs/walkthroughs/index.tsx
deleted file mode 100644
index 2312bb6efc8..00000000000
--- a/docs/pages/docs/walkthroughs/index.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { jsx } from '@emotion/react'
-
-import { Type } from '../../../components/primitives/Type'
-import { DocsPage } from '../../../components/Page'
-import { Well } from '../../../components/primitives/Well'
-import { useMediaQuery } from '../../../lib/media'
-import { InlineCode } from '../../../components/primitives/Code'
-
-export function QuickStart () {
- const mq = useMediaQuery()
- return (
-
-
-
- Take a tour of Keystone in minutes with our CLI starter project
-
-
-
- )
-}
-
-export function Foundations () {
- const mq = useMediaQuery()
- return (
-
-
-
- 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
-
-
-
- )
-}
-
-export default function Docs () {
- return (
-
-
- Walkthroughs
-
-
-
- Step-by-step tutorials for building with Keystone.
-
-
-
- Getting started
-
-
-
- If you’re new to Keystone begin here. These walkthroughs introduce the system, key concepts,
- and show you how to get up and running with schema-driven development the Keystone way.
-
-
-
-
-
- Learn Keystone
-
-
-
- Learn how to build a functioning blog backend with relationships, auth, and session data
- from an empty folder, and gain insights into Keystone’s core concepts along the way.
-
-
-
-
- )
-}
diff --git a/docs/pages/updates/index.tsx b/docs/pages/updates/index.tsx
deleted file mode 100644
index 50a3ff8e55d..00000000000
--- a/docs/pages/updates/index.tsx
+++ /dev/null
@@ -1,918 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { type HTMLAttributes, type ReactNode } from 'react'
-import { jsx } from '@emotion/react'
-import Link from 'next/link'
-
-import { InlineCode } from '../../components/primitives/Code'
-import { Button } from '../../components/primitives/Button'
-import { Alert } from '../../components/primitives/Alert'
-import { Type } from '../../components/primitives/Type'
-import { DocsPage } from '../../components/Page'
-import { ArrowR } from '../../components/icons/ArrowR'
-import { Emoji } from '../../components/primitives/Emoji'
-import { useMediaQuery } from '../../lib/media'
-
-type TimelineProps = {
- date: string
- isLatest?: boolean
- isFirst?: boolean
-} & HTMLAttributes
-
-function Timeline ({ date, isLatest, isFirst, ...props }: TimelineProps) {
- return (
-
-
- {isLatest && (
-
- )}
-
-
-
- {date}
-
-
- )
-}
-
-type BoxProps = {
- link?: string
- heading?: ReactNode
- children: ReactNode
-} & HTMLAttributes
-
-function Box ({ link, heading, children, ...props }: BoxProps) {
- return (
-
- {heading && (
-
- {heading}
-
- )}
-
- {children}
-
- {link && (
-
- Read more
-
- )}
-
- )
-}
-
-export default function WhatsNew () {
- const mq = useMediaQuery()
-
- return (
-
-
- Latest News
-
-
-
- A snapshot of Keystone improvements and community happenings.
-
- For developer changelogs see our{' '}
- GitHub Releases .
-
-
-
-
- What are we working on next?{' '}
-
- See our{' '}
-
- Roadmap
-
-
-
-
-
-
-
-
- Now you can provide additional context to the reader of Admin UI form fields. Use them to
- provide a better editing experience and improve the quality of what goes in to your
- system.
-
-
-
-
- We’ve added support for MySQL to Keystone's list of DB providers, bringing the total
- number of supported DB types to three.
-
-
-
- It’s now much easier to get Images and Files working with Keystone’s new{' '}
- storage configuration object.{' '}
-
-
-
- Document field components now accept re-orderable arrays. Drag and drop your way to
- carousels, galleries, recipe steps and more. Look for the Carousel component in our{' '}
- Document Field demo.
-
-
-
- You can now experiment with Keystone{' '}
- example projects {' '}
- in a browser using the free codesandbox.io service.
- Try the{' '}
-
- blog example
- {' '}
- today. Thanks @murznn for making it happen!{' '}
-
-
-
-
- Aimed at Keystone beginners, our{' '}
- new learning series shows you how to
- turn an empty folder into a functioning blog backend with relationships, auth, and session
- data.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Keystone 6 is now in General Availability! Today's Keystone is faster and more flexible
- than it's ever been, and is ready for you to build amazing things with{' '}
- {' '}
- Read the full story here.
-
-
-
- Keystone now uses Prisma’s Node-API Query Engine. Query times are now much faster,
- especially for large data sets.
-
-
-
- We now have a brand portal full of logos, monograms and
- assets. Perfect for your blog posts, plugins and more.
-
-
-
-
- This example
- {' '}
- uses{' '}
-
- Nexus
- {' '}
- — a declarative, code-first and strongly typed GraphQL schema construction for TypeScript
- & JavaScript —to extend the GraphQL API provided by Keystone with custom queries and
- mutations.
-
-
-
- The image and files configuration options have been removed from Keystone's configuration,
- and a new storage configuration object introduced. timestamp ,{' '}
- float and decimal fields with{' '}
- isIndexed: 'unique' now have unique filters via
- ListWhereUniqueInput .
-
-
-
- You can now use different table and column names for your list and field keys. Powered by
- Prisma's @map and @@map attributes: this
- is useful if you don’t want to modify your existing database (such as a read-only
- database) and use it with Keystone.
-
-
-
- With keystone-next dev you can now update your GraphQL schema,
- change hooks and access control, log errors to see how your data returns, then immediately
- use the playground to test it and iterate. This is on top of to the current support for
- live reloading changes to custom views in the Admin UI.
-
-
-
-
- This example
- {' '}
- shows you how to deploy Keystone to Azure PaaS using AppService, Storage and Postgres.
- Thanks to community member Aaron Powell for the awesome contribution{' '}
-
-
-
-
-
- This example
- {' '}
- shows you how to create REST endpoints by extending Keystone’s express app so you can use
- the Query API to execute queries against the schema.
-
- {/*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Taking the chance to introduce the core team and address some community questions in
- person, the team held an online Q&A event and launched a YouTube channel at the same time.{' '}
-
- Watch it online here
-
- .
- */}
-
-
- Our CLI app now uses SQLite under the hood so you don’t have to
- spend time on DB config when trying out new ideas. We also updated the{' '}
- getting started walkthrough to reflect this
- improvement.
-
-
-
- Learn how to get your Keystone project on the web with our new one-click starters for{' '}
-
- Heroku
- {' '}
- and{' '}
-
- Railway
-
- .
-
-
-
- Jed spoke at Prisma Meetup Korea, covering V6 general availability, user-facing
- management, UI authentication, access control, business logic integrations and more.{' '}
-
- Watch it online here
-
- .
-
-
-
- Major release #2 of #3 planned ahead of Keystone 6 General Availability includes:
-
-
- A better Access Control API
-
- Customisable Express + GraphQL API paths
- Apollo Server introspection
- Omit GraphQL operations
- Faster startups in local dev
- Keystone has been updated to Next.js v11
-
-
-
-
- Access Control is now easier to program, and makes it harder to introduce security gaps in
- your system.
-
-
- The static approach to access control has been replaced. Now access
- control never effects the operations in your GraphQL API.
-
-
- Keystone used to return an access denied error from a{' '}
- query if an item couldn't be found, or explicitly had access denied.
- The improved API never returns that error type on a query .
-
-
- Access rules are now more explicit, and support fewer variations so
- you're less likely to introduce security gaps.
-
-
- To securely upgrade your system, follow the instructions in our{' '}
- Access Control upgrade guide.
-
-
-
- A long awaited feature, the Express App that Keystone creates is now{' '}
- customisable with the new{' '}
- extendExpressApp option:
-
- Add your own custom server routes
- Host two apps on separate ports
- And more...
-
-
-
-
- The GraphQL endpoint accessible by default at `/api/graphql` can now be customised with
- the new option config.graphql.path . You can find this and all
- other options in our GraphQL API docs.
-
-
-
- A major milestone in the path to a General Availability status
- for Keystone 6 , we've just released a new and improved GraphQL API.{' '}
-
-
-
- We’ve made the experience of working with Keystone’s GraphQL API easier to program and
- reason about. Be sure to check our{' '}
- GraphQL API docs.
-
-
-
- We're opening Admin UI up to support a more personal content experience. Now you can:
-
-
- embed your own custom logo,
-
-
- add custom pages with Admin UI
- routes, and
-
-
- link to them (and elsewhere) with{' '}
- custom navigation.
-
-
- To deliver a more productive editor experience that's aligned with the needs and brand of
- your organisation.
-
-
-
- We've added an optional /_healthcheck endpoint to Keystone's
- express server. Use it to ensure your Keystone instance is up and running with website
- monitoring solutions.
-
-
-
-
-
-
-
- Follow along in with the repo
- {' '}
- as Jed builds a front and back-end for a Blog app with Prisma, KeystoneJS, GraphQL,
- Next.js and Tailwind, that gives you:
-
-
- Public auth and signup
-
-
- Role-based access control
-
-
- Custom components in the{' '}
- document field
-
-
- Editors can embed audience Polls in post content, and authenticated visitors can make
- their vote count in the frontend.
-
-
-
- Learn how to create a{' '}
-
- custom field view
- {' '}
- for a JSON field that lets users users add, edit and remove
- navigation items from a list.
-
-
-
- Jed's talk at Prisma Day 2021 is a great overview into what makes Keystone special. Watch
- below, or read the full transcript.
-
-
-
-
-
-
- We've launched our new website for Keystone 6 ! There’s a new home page,
- and background on why Keystone is built for projects
- that need to scale on their own terms. Navigating the docs is easier with breadcrumbs,
- index pages for Walkthroughs,{' '}
- Guides, and APIs
- , and a better mobile experience. We hope you like it
-
-
-
- In our contuing efforts to improve the developer documentation for Keystone 6, we’ve
- written the following guides:
-
-
- Virtual fields
-
-
- Relationships
-
-
- Hooks
-
-
- Query Filters
-
-
- Testing
-
-
-
-
-
- After months of work deep in the codebase, Keystone 6 now has a new core. This unblocks a
- bunch of roadmap features like custom field types, GraphQL Schema extensions, and more.
- The new core does bring some minor behavioural changes to Keystone’s APIs. See the release
- notes for more information.
-
-
-
- We’ve made accessibility updates to DatePicker labels,{' '}
- relationship fields, as well as visual improvements to segment
- control (when no value is selected), and more.
-
-
-
- A long awaited feature: you can now find an item by unique fields in your schema. It works
- for{' '}
-
- text
- {' '}
- and{' '}
-
- integer
- {' '}
- fields that have isUnique: true set.
-
-
-
- You can now use JSON blobs in your backend, and provide your own React UI components to
- edit them. Try it out in this{' '}
-
- example project
-
- . It accepts any valid JSON node including:
-
-
- string
-
-
- number
-
-
- array
-
-
- object
-
-
-
-
-
- We now have a{' '}
-
- collection of example projects
- {' '}
- you can run locally to learn more about a particular feature of Keystone. Each project
- comes with explainers on the how and why. Use them as a reference for best practice, and
- as a jumping off point when adding features to your own Keystone project.
-
-
-
- We’ve pruned a lot of code to make way for a more efficient and productive core in
- Keystone 6. Changes include:
-
-
- Removed DB adapters and many redundant methods and arguments (now that Keystone 6 uses
- Prisma under the hood).
-
-
- Exchanged deploy , reset and{' '}
- generate commands for{' '}
- keystone-next prisma commands.
-
-
-
-
-
- Keystone 5 is now in maintenance mode while we focus all our efforts on building Keystone
- 6. If you’re wondering which version to start your next project with, this guide is for
- you.
-
-
-
-
- Need answers to Keystone questions? Get help in our
-
-
- Community Slack
-
-
-
- )
-}
diff --git a/docs/pages/updates/roadmap.tsx b/docs/pages/updates/roadmap.tsx
deleted file mode 100644
index 12afb2ffeb5..00000000000
--- a/docs/pages/updates/roadmap.tsx
+++ /dev/null
@@ -1,490 +0,0 @@
-/** @jsxRuntime classic */
-/** @jsx jsx */
-import { type ComponentProps, Fragment, type ReactNode } from 'react'
-import { jsx } from '@emotion/react'
-import Link from 'next/link'
-
-import { InlineCode } from '../../components/primitives/Code'
-import { Highlight } from '../../components/primitives/Highlight'
-import { Gradient } from '../../components/primitives/Gradient'
-import { Alert } from '../../components/primitives/Alert'
-import { Emoji } from '../../components/primitives/Emoji'
-import { Type } from '../../components/primitives/Type'
-import { DocsPage } from '../../components/Page'
-import { useMediaQuery } from '../../lib/media'
-
-function TimelineItem ({ children }: { children: ReactNode }) {
- return (
-
- {children}
-
- )
-}
-
-function TimelineMarker ({ look }: Pick, 'look'>) {
- return (
-
- )
-}
-
-function TimelineWeAreHere () {
- const arrowSize = '0.4rem'
- const mq = useMediaQuery()
- return (
-
-
- We are here!
-
- )
-}
-
-type TimelineContentProps = {
- title: ReactNode
- children: ReactNode
-} & Pick, 'look'>
-function TimelineContent ({ title, look, children }: TimelineContentProps) {
- return (
-
-
- {title}
-
-
- {children}
-
-
- )
-}
-
-type RoadmapListProps = {
- children: ReactNode
-}
-
-function RoadmapList ({ children }: RoadmapListProps) {
- const mq = useMediaQuery()
- return (
-
- )
-}
-
-const roadmapItemSectionStyles = {
- margin: '0rem 0 .75rem',
- borderRadius: '.4rem',
- display: 'inline-block',
- padding: '0.1825rem 0.5rem',
- fontSize: '0.9rem',
- fontWeight: 700,
-}
-const roadmapItemSection = {
- docs: () => (
- Docs
- ),
- 'fields and schema': () => (
-
- Fields & Schema
-
- ),
- core: () => (
- Core
- ),
- 'admin ui': () => (
-
- Admin UI
-
- ),
-}
-
-type RoadmapItemProps = {
- title: ReactNode
- section?: keyof typeof roadmapItemSection
- children: ReactNode
-}
-function RoadmapItem ({ title, section, children }: RoadmapItemProps) {
- const Section = section ? roadmapItemSection[section] : null
- return (
-
- {Section && }
-
- {title}
-
-
- {children}
-
-
- )
-}
-
-function Divider () {
- return (
-
- )
-}
-
-export default function Roadmap () {
- const mq = useMediaQuery()
-
- return (
-
-
- Roadmap
-
-
- After a year of intensive development Keystone 6 has achieved a{' '}
- General Availability release! We've graduated
- to the
- @keystone-6 namespace on npm and have a stable set of APIs that you
- can confidently build on
-
-
-
-
-
- Surveyed the landscape. Formed concepts around what a next-gen experience would look
- like. Started laying the foundations.
-
-
-
-
-
- Published the New Interfaces as @keystone-next
- on npm. Commenced iteration based on community & internal feedback.
-
-
-
-
-
-
- Stabilised the new architecture & APIs. Docs & example projects. Published as{' '}
- @keystone-6 on npm.
-
-
-
-
-
- A better dev-configured editing experience. Maturity & features in Keystone core. More
- pathways to grow with Keystone.
-
-
-
-
- What's Next
-
-
- We're using the foundations we shipped in 2021 as a springboard to bring{' '}
- developers,{' '}
- project owners, and{' '}
- editors into more productive ways of working
- together. Our key areas of focus for 2022 are:
-
-
-
-
- A next-gen Admin UI that unifies developer and editor collaborations in
- new and exciting ways
-
-
- Maturing the DX with better Types and capabilities for self-hosting and
- media management
-
-
- Enabling community with more pathways for you to learn and grow with
- Keystone
-
-
-
-
- Next-gen Admin UI
-
-
- Our design team spent much of the second half of 2021 defining a new vision for Admin UI
- that gives you more capabilities to support content editors in ways that matter most to
- them. We've already shipped quick wins for customisable{' '}
- logos,{' '}
- pages, and{' '}
- navigation, but the really
- transformative features (that rely on more extensive customisation) are still in the works.
- This body of work will elevate the experience of authoring content in Keystone to the same
- high standards we have for authoring with Keystone's core APIs.
-
-
- Maturing the Developer Experience
-
-
- We'll continue to iterate on making Keystone the easiest way to design and standup a GraphQL
- API on the web. Going all-in on Typescript has made the DX so sweet, but we want to take it{' '}
- further so that Keystone's the best Typescript > GraphQL developer experience in
- the ecosystem.
-
-
- We'll also focus on making better pathways for you to integrate Keystone with the deployment
- services you use most, so you can get the most out of where the modern web is going. Image
- and file management is an area we're actively iterating on, and we'll have more to share
- soon.
-
-
- Enabling the community
-
-
- At{' '}
-
- Thinkmill
-
- , we have a longstanding commitment to open source that includes the likes of{' '}
-
- react select
-
- ,{' '}
-
- classnames
-
- , and of course Keystone . Now that Keystone 6 is stable
- enough to develop features and integrations around, we'll put better processes in place for
- you to collaborate around the Keystone project and showcase your awesome work with others in
- the community.
-
-
- Feature Roadmap
-
-
-
- To see what we've recently shipped, checkout our updates and{' '}
- release notes
-
-
-
- Current focus
-
-
-
-
- A way to define a single object in schema that's editable in Admin UI and accessible in
- the GraphQL API. Handy for storing website & social settings, API keys, and more.
-
-
-
-
- Dynamically showing fields based on the value of other fields is a great way to improve
- editing flow and content integrity.
-
-
-
-
- It's often easier to work with content when the form is grouped into different sections
- of related fields.
-
-
-
-
-
- Next up
-
-
-
-
- Sometimes you need to manage data in structures that are nested and/or repeating. We're
- working on a way to define these in schema and have them stored as JSON field in the
- database.
-
-
-
-
- Access your GraphQL APIs from Node.js for greater flexibility when writing apps and
- hybrid use-cases.
-
-
-
-
- Certain list types come with a need to order the items they contain. We're looking in to
- an approach that's easy to implement in schema.
-
-
-
-
- We're broadening our list of streamlined scenarios & looking into options for serverless
- environments.
-
-
-
-
-
- Further afield
-
-
-
-
- When an English-language UI doesn't work for your team there'll be a way for you to add
- translations to all the strings in Admin UI.
-
-
-
-
- An editing interface that's available for you to use no matter what device you're on.
-
-
-
-
- Solving accessibility in a customisable editing interface is a hard problem. We're up
- for the challenge.
-
-
-
-
- Built-in tooling for you to give editors a sense of how their content will be consumed
- by end users.
-
-
-
-
- Design your own journey from draft to{' '}
- published to meet the unique needs of your content team.
-
-
-
-
- More powerful interfaces for managing different scales of related data, from small to
- really really large
-
-
-
-
- Long lasting server operations that can change their result over time. Handy for
- updating your front-end in real time when important data changes in your backend.
-
-
-
-
- A built-in schema-driven approach to supporting multilingual content projects.
-
-
-
-
- If you want to update an item but aren't sure if it exists, this will update the item if
- it's there or create a new item with the data you've provided.
-
-
-
-
- The future of deployment is serverless, and we're tracking the state of the ecosystem to
- make sure Keystone is ready for it.
-
-
-
-
- A way for you and editors to easily search for strings across your entire dataset. Handy
- for when you need something specific but don't know where it lives.
-
-
-
-
-
- Got a feature you're after, or want to know more about the future?
-
-
- Join the Keystone conversation on{' '}
-
- Slack
-
- ,{' '}
-
- GitHub
-
- , and{' '}
-
- Twitter
-
- .
-
-
-
- )
-}
diff --git a/docs/public/assets/getting-started/cover.svg b/docs/public/assets/getting-started/cover.svg
index 7117e72e990..2438440759f 100644
--- a/docs/public/assets/getting-started/cover.svg
+++ b/docs/public/assets/getting-started/cover.svg
@@ -13,8 +13,8 @@
λ
- yarn create keystone-app
- yarn create v1.22.10
+ npm create keystone-app
+
[1/4]
🔍
@@ -46,7 +46,7 @@
To launch your app, run:
- cd my-app
- - yarn dev
+ - npm run dev
Next steps:
diff --git a/docs/public/sitemap-0.xml b/docs/public/sitemap-0.xml
index d775b6be305..318dc2a6eaf 100644
--- a/docs/public/sitemap-0.xml
+++ b/docs/public/sitemap-0.xml
@@ -1,83 +1,82 @@
-https://keystonejs.com 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/blog 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/branding 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/overview 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/examples 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/document-field-demo 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/overview 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/walkthroughs 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/enterprise 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/for-content-management 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/for-developers 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/for-organisations 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/updates 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/updates/roadmap 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/why-keystone 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/access-control 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/auth 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/config 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/hooks 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/lists 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/config/session 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/context/db-items 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/context/get-context 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/context/overview 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/context/query 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/bigint 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/calendarday 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/checkbox 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/cloudinaryimage 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/decimal 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/document 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/file 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/float 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/image 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/integer 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/json 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/multiselect 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/overview 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/password 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/relationship 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/select 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/text 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/timestamp 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/fields/virtual 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/getting-started 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/graphql/filters 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/graphql/overview 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/auth-and-access-control 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/choosing-a-database 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/cli 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/custom-admin-ui-logo 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/custom-admin-ui-navigation 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/custom-admin-ui-pages 2024-07-08T03:24:41.483Z daily 0.7
-https://keystonejs.com/docs/guides/custom-field-views 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/custom-fields 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/database-migration 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/document-fields 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/filters 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/hooks 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/images-and-files 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/relationships 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/schema-extension 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/testing 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/guides/virtual-fields 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/reference/telemetry 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/walkthroughs/lesson-1 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/walkthroughs/lesson-2 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/walkthroughs/lesson-3 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/walkthroughs/lesson-4 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/docs/walkthroughs/lesson-5 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/customisable-admin-ui 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/embedded-mode-with-sqlite-nextjs 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/general-availability 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/introducing-keystone-blog 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/keystone-tutorials 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/mysql-support 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/nextjs-keystone 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/prisma-day-2021 2024-07-08T03:24:41.484Z daily 0.7
-https://keystonejs.com/blog/singleton 2024-07-08T03:24:41.484Z daily 0.7
+https://keystonejs.com/docs 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/examples 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/enterprise 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/for-organisations 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/roadmap 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/why-keystone 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/overview 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/for-content-management 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/customisable-admin-ui 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/embedded-mode-with-sqlite-nextjs 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/general-availability 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/introducing-keystone-blog 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/keystone-tutorials 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/mysql-support 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/nextjs-keystone 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/prisma-day-2021 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog/singleton 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/for-developers 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/document-field-demo 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/blog 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/overview 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/branding 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/access-control 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/auth 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/config 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/hooks 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/lists 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/config/session 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/context/db-items 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/context/get-context 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/context/overview 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/context/query 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/bigint 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/calendarday 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/checkbox 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/cloudinaryimage 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/decimal 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/document 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/file 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/float 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/image 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/integer 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/json 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/multiselect 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/overview 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/password 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/relationship 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/select 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/text 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/timestamp 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/fields/virtual 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/getting-started 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/graphql/filters 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/graphql/overview 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/auth-and-access-control 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/choosing-a-database 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/cli 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/custom-admin-ui-logo 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/custom-admin-ui-navigation 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/custom-admin-ui-pages 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/custom-field-views 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/custom-fields 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/database-migration 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/document-fields 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/filters 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/hooks 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/images-and-files 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/relationships 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/schema-extension 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/testing 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/guides/virtual-fields 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/reference/telemetry 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs/lesson-1 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs/lesson-2 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs/lesson-3 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs/lesson-4 2024-08-09T00:04:47.022Z daily 0.7
+https://keystonejs.com/docs/walkthroughs/lesson-5 2024-08-09T00:04:47.022Z daily 0.7
\ No newline at end of file
diff --git a/docs/redirects.mjs b/docs/redirects.mjs
index 21fc60fcace..e1e2fca7ba7 100644
--- a/docs/redirects.mjs
+++ b/docs/redirects.mjs
@@ -47,7 +47,7 @@ const KEYSTONE_5 = [
// Old roadmap URL just redirects to the new roadmap
{
source: '/guides/road-map',
- destination: '/updates/roadmap',
+ destination: '/roadmap',
permanent: true,
},
// Linked to from google results (2021-06-28) and possibly elsewhere?
@@ -93,7 +93,8 @@ const ORIGINAL_NEXT = [
destination: 'https://github.com/keystonejs/keystone/releases',
permanent: true,
},
- { source: '/roadmap', destination: '/updates/roadmap', permanent: true },
+ { source: '/updates', destination: '/blog', permanent: true },
+ { source: '/updates/roadmap', destination: '/roadmap', permanent: true },
{ source: '/whats-new', destination: '/updates/whats-new-in-v6', permanent: true },
]
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index a4c1f66a19b..8ce8e6d3e05 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -10,8 +10,8 @@
"esModuleInterop": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
- "isolatedModules": true,
"jsx": "preserve",
+ "isolatedModules": true,
"plugins": [
{
"name": "next"
diff --git a/eslint.config.js b/eslint.config.js
index 0f4f6d17b92..e589500963a 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -7,6 +7,7 @@ module.exports = tseslint.config(
{
ignores: [
'**/.keystone/',
+ '**/.next/',
'**/dist/',
'**/node_modules/',
'**/syntax-error.js',
diff --git a/examples/assets-local/package.json b/examples/assets-local/package.json
index 425d98a6be2..46afd7aff39 100644
--- a/examples/assets-local/package.json
+++ b/examples/assets-local/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-6/example-assets-local",
- "version": "0.0.8",
+ "verison": null,
"private": true,
"license": "MIT",
"scripts": {
@@ -10,11 +10,11 @@
"postinstall": "keystone postinstall"
},
"dependencies": {
- "@keystone-6/core": "workspace:^",
- "@prisma/client": "catalog:"
+ "@keystone-6/core": "^6.2.0",
+ "@prisma/client": "5.17.0"
},
"devDependencies": {
- "prisma": "catalog:",
- "typescript": "catalog:"
+ "prisma": "5.17.0",
+ "typescript": "^5.5.0"
}
}
diff --git a/examples/assets-local/schema.graphql b/examples/assets-local/schema.graphql
index aa2c66202d4..184a1f98fea 100644
--- a/examples/assets-local/schema.graphql
+++ b/examples/assets-local/schema.graphql
@@ -139,8 +139,8 @@ type Mutation {
}
type Query {
- posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
post(where: PostWhereUniqueInput!): Post
+ posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
postsCount(where: PostWhereInput! = {}): Int
keystone: KeystoneMeta!
}
@@ -170,6 +170,7 @@ type KeystoneAdminUIListMeta {
labelField: String!
fields: [KeystoneAdminUIFieldMeta!]!
groups: [KeystoneAdminUIFieldGroupMeta!]!
+ graphql: KeystoneAdminUIGraphQL!
initialSort: KeystoneAdminUISort
isHidden: Boolean!
isSingleton: Boolean!
@@ -242,6 +243,33 @@ type KeystoneAdminUIFieldGroupMeta {
fields: [KeystoneAdminUIFieldMeta!]!
}
+type KeystoneAdminUIGraphQL {
+ names: KeystoneAdminUIGraphQLNames!
+}
+
+type KeystoneAdminUIGraphQLNames {
+ outputTypeName: String!
+ whereInputName: String!
+ whereUniqueInputName: String!
+ createInputName: String!
+ createMutationName: String!
+ createManyMutationName: String!
+ relateToOneForCreateInputName: String!
+ relateToManyForCreateInputName: String!
+ itemQueryName: String!
+ listOrderName: String!
+ listQueryCountName: String!
+ listQueryName: String!
+ updateInputName: String!
+ updateMutationName: String!
+ updateManyInputName: String!
+ updateManyMutationName: String!
+ relateToOneForUpdateInputName: String!
+ relateToManyForUpdateInputName: String!
+ deleteMutationName: String!
+ deleteManyMutationName: String!
+}
+
type KeystoneAdminUISort {
field: String!
direction: KeystoneAdminUISortDirection!
diff --git a/examples/assets-s3/package.json b/examples/assets-s3/package.json
index 5d5c98c2bdd..c963ca58be7 100644
--- a/examples/assets-s3/package.json
+++ b/examples/assets-s3/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-6/example-assets-s3",
- "version": "0.0.7",
+ "version": null,
"private": true,
"license": "MIT",
"scripts": {
@@ -10,12 +10,12 @@
"postinstall": "keystone postinstall"
},
"dependencies": {
- "@keystone-6/core": "workspace:^",
- "@prisma/client": "catalog:",
+ "@keystone-6/core": "^6.2.0",
+ "@prisma/client": "5.17.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
- "prisma": "catalog:",
- "typescript": "catalog:"
+ "prisma": "5.17.0",
+ "typescript": "^5.5.0"
}
}
diff --git a/examples/assets-s3/schema.graphql b/examples/assets-s3/schema.graphql
index aa2c66202d4..184a1f98fea 100644
--- a/examples/assets-s3/schema.graphql
+++ b/examples/assets-s3/schema.graphql
@@ -139,8 +139,8 @@ type Mutation {
}
type Query {
- posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
post(where: PostWhereUniqueInput!): Post
+ posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
postsCount(where: PostWhereInput! = {}): Int
keystone: KeystoneMeta!
}
@@ -170,6 +170,7 @@ type KeystoneAdminUIListMeta {
labelField: String!
fields: [KeystoneAdminUIFieldMeta!]!
groups: [KeystoneAdminUIFieldGroupMeta!]!
+ graphql: KeystoneAdminUIGraphQL!
initialSort: KeystoneAdminUISort
isHidden: Boolean!
isSingleton: Boolean!
@@ -242,6 +243,33 @@ type KeystoneAdminUIFieldGroupMeta {
fields: [KeystoneAdminUIFieldMeta!]!
}
+type KeystoneAdminUIGraphQL {
+ names: KeystoneAdminUIGraphQLNames!
+}
+
+type KeystoneAdminUIGraphQLNames {
+ outputTypeName: String!
+ whereInputName: String!
+ whereUniqueInputName: String!
+ createInputName: String!
+ createMutationName: String!
+ createManyMutationName: String!
+ relateToOneForCreateInputName: String!
+ relateToManyForCreateInputName: String!
+ itemQueryName: String!
+ listOrderName: String!
+ listQueryCountName: String!
+ listQueryName: String!
+ updateInputName: String!
+ updateMutationName: String!
+ updateManyInputName: String!
+ updateManyMutationName: String!
+ relateToOneForUpdateInputName: String!
+ relateToManyForUpdateInputName: String!
+ deleteMutationName: String!
+ deleteManyMutationName: String!
+}
+
type KeystoneAdminUISort {
field: String!
direction: KeystoneAdminUISortDirection!
diff --git a/examples/auth/app/(admin)/.admin/index.tsx b/examples/auth/app/(admin)/.admin/index.tsx
new file mode 100644
index 00000000000..cc237ef7f98
--- /dev/null
+++ b/examples/auth/app/(admin)/.admin/index.tsx
@@ -0,0 +1,21 @@
+/* eslint-disable */
+import * as view0 from "@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view"
+import * as view1 from "@keystone-6/core/fields/types/text/views"
+import * as view2 from "@keystone-6/core/fields/types/password/views"
+import * as view3 from "@keystone-6/core/fields/types/checkbox/views"
+import * as view4 from "@keystone-6/core/fields/types/timestamp/views"
+import * as view5 from "@keystone-6/core/fields/types/relationship/views"
+import * as view6 from "@keystone-6/core/fields/types/integer/views"
+import * as view7 from "@keystone-6/core/fields/types/select/views"
+import * as view8 from "@keystone-6/core/fields/types/json/views"
+
+const adminConfig = {}
+
+export const config = {
+ lazyMetadataQuery: {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"keystone","loc":{"start":22,"end":30}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminMeta","loc":{"start":39,"end":48}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lists","loc":{"start":59,"end":64}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key","loc":{"start":77,"end":80}},"arguments":[],"directives":[],"loc":{"start":77,"end":80}},{"kind":"Field","name":{"kind":"Name","value":"isHidden","loc":{"start":91,"end":99}},"arguments":[],"directives":[],"loc":{"start":91,"end":99}},{"kind":"Field","name":{"kind":"Name","value":"fields","loc":{"start":110,"end":116}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path","loc":{"start":131,"end":135}},"arguments":[],"directives":[],"loc":{"start":131,"end":135}},{"kind":"Field","name":{"kind":"Name","value":"createView","loc":{"start":148,"end":158}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fieldMode","loc":{"start":175,"end":184}},"arguments":[],"directives":[],"loc":{"start":175,"end":184}}],"loc":{"start":159,"end":198}},"loc":{"start":148,"end":198}}],"loc":{"start":117,"end":210}},"loc":{"start":110,"end":210}}],"loc":{"start":65,"end":220}},"loc":{"start":59,"end":220}}],"loc":{"start":49,"end":228}},"loc":{"start":39,"end":228}}],"loc":{"start":31,"end":234}},"loc":{"start":22,"end":234}},{"kind":"Field","name":{"kind":"Name","value":"authenticatedItem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]},
+ fieldViews: [view0,view1,view2,view3,view4,view5,view6,view7,view8],
+ adminMetaHash: '1c8ah28',
+ adminConfig,
+ apiPath: '/api/graphql',
+ adminPath: '',
+};
diff --git a/examples/auth/app/(admin)/[listKey]/[id]/page.tsx b/examples/auth/app/(admin)/[listKey]/[id]/page.tsx
new file mode 100644
index 00000000000..c5d7ea2be62
--- /dev/null
+++ b/examples/auth/app/(admin)/[listKey]/[id]/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { ItemPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage'
+
+export default ItemPage
diff --git a/examples/auth/app/(admin)/[listKey]/create/page.tsx b/examples/auth/app/(admin)/[listKey]/create/page.tsx
new file mode 100644
index 00000000000..d6042acaa96
--- /dev/null
+++ b/examples/auth/app/(admin)/[listKey]/create/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { CreateItemPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage'
+
+export default CreateItemPage
diff --git a/examples/auth/app/(admin)/[listKey]/page.tsx b/examples/auth/app/(admin)/[listKey]/page.tsx
new file mode 100644
index 00000000000..f6e75f8cfab
--- /dev/null
+++ b/examples/auth/app/(admin)/[listKey]/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { ListPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage'
+
+export default ListPage
diff --git a/examples/auth/app/(admin)/init/page.tsx b/examples/auth/app/(admin)/init/page.tsx
new file mode 100644
index 00000000000..224220389cb
--- /dev/null
+++ b/examples/auth/app/(admin)/init/page.tsx
@@ -0,0 +1,7 @@
+'use client'
+/* eslint-disable */
+import { getInitPage } from '@keystone-6/auth/pages/InitPage'
+
+const fieldPaths = ["name","password"]
+
+export default getInitPage({"listKey":"User","fieldPaths":["name","password"],"enableWelcome":true})
diff --git a/examples/auth/app/(admin)/layout.tsx b/examples/auth/app/(admin)/layout.tsx
new file mode 100644
index 00000000000..abb5a0f3b2c
--- /dev/null
+++ b/examples/auth/app/(admin)/layout.tsx
@@ -0,0 +1,16 @@
+'use client'
+import { Layout } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App'
+import { config } from './.admin'
+
+
+export default function AdminLayout ({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
\ No newline at end of file
diff --git a/examples/auth/app/(admin)/no-access/page.tsx b/examples/auth/app/(admin)/no-access/page.tsx
new file mode 100644
index 00000000000..00571f2f9b3
--- /dev/null
+++ b/examples/auth/app/(admin)/no-access/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { getNoAccessPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage'
+
+export default getNoAccessPage({ sessionsEnabled: true })
diff --git a/examples/auth/app/(admin)/page.tsx b/examples/auth/app/(admin)/page.tsx
new file mode 100644
index 00000000000..5c268390b0f
--- /dev/null
+++ b/examples/auth/app/(admin)/page.tsx
@@ -0,0 +1,2 @@
+'use client'
+export { HomePage as default } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage'
diff --git a/examples/auth/app/(admin)/signin/page.tsx b/examples/auth/app/(admin)/signin/page.tsx
new file mode 100644
index 00000000000..ef30e38dcac
--- /dev/null
+++ b/examples/auth/app/(admin)/signin/page.tsx
@@ -0,0 +1,5 @@
+'use client'
+/* eslint-disable */
+import { getSigninPage } from '@keystone-6/auth/pages/SigninPage'
+
+export default getSigninPage({"identityField":"name","secretField":"password","mutationName":"authenticateUserWithPassword","successTypename":"UserAuthenticationWithPasswordSuccess","failureTypename":"UserAuthenticationWithPasswordFailure"})
diff --git a/examples/auth/app/layout.tsx b/examples/auth/app/layout.tsx
new file mode 100644
index 00000000000..38a4853e3a5
--- /dev/null
+++ b/examples/auth/app/layout.tsx
@@ -0,0 +1,11 @@
+export default function RootLayout ({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/examples/auth/keystone.ts b/examples/auth/keystone.ts
index 88f7606d0a1..fb1b868a49b 100644
--- a/examples/auth/keystone.ts
+++ b/examples/auth/keystone.ts
@@ -71,5 +71,5 @@ export default withAuth>(
// the session secret is used to encrypt cookie data
secret: sessionSecret,
}),
- })
+ }) as any
)
diff --git a/examples/nextjs-keystone-two-servers/nextjs-frontend/next-env.d.ts b/examples/auth/next-env.d.ts
similarity index 100%
rename from examples/nextjs-keystone-two-servers/nextjs-frontend/next-env.d.ts
rename to examples/auth/next-env.d.ts
diff --git a/examples/auth/next.config.mjs b/examples/auth/next.config.mjs
new file mode 100644
index 00000000000..8f06b183bdf
--- /dev/null
+++ b/examples/auth/next.config.mjs
@@ -0,0 +1,10 @@
+// you don't need this if you're building something outside of the Keystone repo
+
+export default {
+ eslint: {
+ ignoreDuringBuilds: true,
+ },
+ typescript: {
+ ignoreBuildErrors: true,
+ },
+}
diff --git a/examples/auth/package.json b/examples/auth/package.json
index 7e757dcb1dc..4f733d35ec2 100644
--- a/examples/auth/package.json
+++ b/examples/auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-6/example-auth",
- "version": "0.1.2",
+ "version": null,
"private": true,
"license": "MIT",
"scripts": {
@@ -10,12 +10,13 @@
"postinstall": "keystone postinstall"
},
"dependencies": {
- "@keystone-6/auth": "workspace:^",
- "@keystone-6/core": "workspace:^",
- "@prisma/client": "catalog:"
+ "@keystone-6/auth": "^8.0.0",
+ "@keystone-6/core": "^6.2.0",
+ "@prisma/client": "5.17.0",
+ "next": "^14.2.0"
},
"devDependencies": {
- "prisma": "catalog:",
- "typescript": "catalog:"
+ "prisma": "5.17.0",
+ "typescript": "^5.5.0"
}
}
diff --git a/examples/auth/schema.graphql b/examples/auth/schema.graphql
index 3bbe083ed5c..d6ddcb6aa87 100644
--- a/examples/auth/schema.graphql
+++ b/examples/auth/schema.graphql
@@ -67,6 +67,380 @@ input UserCreateInput {
isAdmin: Boolean
}
+type Project {
+ id: ID!
+ slug: String
+ name: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ deletedAt: DateTime
+ taskRuns(where: TaskRunWhereInput! = {}, orderBy: [TaskRunOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: TaskRunWhereUniqueInput): [TaskRun!]
+ taskRunsCount(where: TaskRunWhereInput! = {}): Int
+}
+
+scalar DateTime @specifiedBy(url: "https://datatracker.ietf.org/doc/html/rfc3339#section-5.6")
+
+input ProjectWhereUniqueInput {
+ id: ID
+ slug: String
+}
+
+input ProjectWhereInput {
+ AND: [ProjectWhereInput!]
+ OR: [ProjectWhereInput!]
+ NOT: [ProjectWhereInput!]
+ id: IDFilter
+ slug: StringFilter
+ name: StringFilter
+ createdAt: DateTimeNullableFilter
+ updatedAt: DateTimeNullableFilter
+ deletedAt: DateTimeNullableFilter
+ taskRuns: TaskRunManyRelationFilter
+}
+
+input StringFilter {
+ equals: String
+ in: [String!]
+ notIn: [String!]
+ lt: String
+ lte: String
+ gt: String
+ gte: String
+ contains: String
+ startsWith: String
+ endsWith: String
+ not: NestedStringFilter
+}
+
+input NestedStringFilter {
+ equals: String
+ in: [String!]
+ notIn: [String!]
+ lt: String
+ lte: String
+ gt: String
+ gte: String
+ contains: String
+ startsWith: String
+ endsWith: String
+ not: NestedStringFilter
+}
+
+input DateTimeNullableFilter {
+ equals: DateTime
+ in: [DateTime!]
+ notIn: [DateTime!]
+ lt: DateTime
+ lte: DateTime
+ gt: DateTime
+ gte: DateTime
+ not: DateTimeNullableFilter
+}
+
+input TaskRunManyRelationFilter {
+ every: TaskRunWhereInput
+ some: TaskRunWhereInput
+ none: TaskRunWhereInput
+}
+
+input ProjectOrderByInput {
+ id: OrderDirection
+ slug: OrderDirection
+ name: OrderDirection
+ createdAt: OrderDirection
+ updatedAt: OrderDirection
+ deletedAt: OrderDirection
+}
+
+input ProjectUpdateInput {
+ slug: String
+ name: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ deletedAt: DateTime
+ taskRuns: TaskRunRelateToManyForUpdateInput
+}
+
+input TaskRunRelateToManyForUpdateInput {
+ disconnect: [TaskRunWhereUniqueInput!]
+ set: [TaskRunWhereUniqueInput!]
+ create: [TaskRunCreateInput!]
+ connect: [TaskRunWhereUniqueInput!]
+}
+
+input ProjectUpdateArgs {
+ where: ProjectWhereUniqueInput!
+ data: ProjectUpdateInput!
+}
+
+input ProjectCreateInput {
+ slug: String
+ name: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ deletedAt: DateTime
+ taskRuns: TaskRunRelateToManyForCreateInput
+}
+
+input TaskRunRelateToManyForCreateInput {
+ create: [TaskRunCreateInput!]
+ connect: [TaskRunWhereUniqueInput!]
+}
+
+type TaskRun {
+ id: ID!
+ number: Int
+ friendlyId: String
+ status: String
+ taskIdentifier: String
+ isTest: Boolean
+ payload: String
+ payloadType: String
+ context: JSON
+ traceId: String
+ spanId: String
+ project: Project
+ createdAt: DateTime
+ updatedAt: DateTime
+ attempts(where: TaskRunAttemptWhereInput! = {}, orderBy: [TaskRunAttemptOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: TaskRunAttemptWhereUniqueInput): [TaskRunAttempt!]
+ attemptsCount(where: TaskRunAttemptWhereInput! = {}): Int
+ startedAt: DateTime
+ completedAt: DateTime
+}
+
+input TaskRunWhereUniqueInput {
+ id: ID
+}
+
+input TaskRunWhereInput {
+ AND: [TaskRunWhereInput!]
+ OR: [TaskRunWhereInput!]
+ NOT: [TaskRunWhereInput!]
+ id: IDFilter
+ number: IntNullableFilter
+ friendlyId: StringFilter
+ status: StringNullableFilter
+ taskIdentifier: StringFilter
+ isTest: BooleanFilter
+ payload: StringFilter
+ payloadType: StringFilter
+ traceId: StringFilter
+ spanId: StringFilter
+ project: ProjectWhereInput
+ createdAt: DateTimeNullableFilter
+ updatedAt: DateTimeNullableFilter
+ attempts: TaskRunAttemptManyRelationFilter
+ startedAt: DateTimeNullableFilter
+ completedAt: DateTimeNullableFilter
+}
+
+input IntNullableFilter {
+ equals: Int
+ in: [Int!]
+ notIn: [Int!]
+ lt: Int
+ lte: Int
+ gt: Int
+ gte: Int
+ not: IntNullableFilter
+}
+
+input StringNullableFilter {
+ equals: String
+ in: [String!]
+ notIn: [String!]
+ lt: String
+ lte: String
+ gt: String
+ gte: String
+ contains: String
+ startsWith: String
+ endsWith: String
+ not: StringNullableFilter
+}
+
+input TaskRunAttemptManyRelationFilter {
+ every: TaskRunAttemptWhereInput
+ some: TaskRunAttemptWhereInput
+ none: TaskRunAttemptWhereInput
+}
+
+input TaskRunOrderByInput {
+ id: OrderDirection
+ number: OrderDirection
+ friendlyId: OrderDirection
+ status: OrderDirection
+ taskIdentifier: OrderDirection
+ isTest: OrderDirection
+ payload: OrderDirection
+ payloadType: OrderDirection
+ traceId: OrderDirection
+ spanId: OrderDirection
+ createdAt: OrderDirection
+ updatedAt: OrderDirection
+ startedAt: OrderDirection
+ completedAt: OrderDirection
+}
+
+input TaskRunUpdateInput {
+ number: Int
+ friendlyId: String
+ status: String
+ taskIdentifier: String
+ isTest: Boolean
+ payload: String
+ payloadType: String
+ context: JSON
+ traceId: String
+ spanId: String
+ project: ProjectRelateToOneForUpdateInput
+ createdAt: DateTime
+ updatedAt: DateTime
+ attempts: TaskRunAttemptRelateToManyForUpdateInput
+ startedAt: DateTime
+ completedAt: DateTime
+}
+
+input ProjectRelateToOneForUpdateInput {
+ create: ProjectCreateInput
+ connect: ProjectWhereUniqueInput
+ disconnect: Boolean
+}
+
+input TaskRunAttemptRelateToManyForUpdateInput {
+ disconnect: [TaskRunAttemptWhereUniqueInput!]
+ set: [TaskRunAttemptWhereUniqueInput!]
+ create: [TaskRunAttemptCreateInput!]
+ connect: [TaskRunAttemptWhereUniqueInput!]
+}
+
+input TaskRunUpdateArgs {
+ where: TaskRunWhereUniqueInput!
+ data: TaskRunUpdateInput!
+}
+
+input TaskRunCreateInput {
+ number: Int
+ friendlyId: String
+ status: String
+ taskIdentifier: String
+ isTest: Boolean
+ payload: String
+ payloadType: String
+ context: JSON
+ traceId: String
+ spanId: String
+ project: ProjectRelateToOneForCreateInput
+ createdAt: DateTime
+ updatedAt: DateTime
+ attempts: TaskRunAttemptRelateToManyForCreateInput
+ startedAt: DateTime
+ completedAt: DateTime
+}
+
+input ProjectRelateToOneForCreateInput {
+ create: ProjectCreateInput
+ connect: ProjectWhereUniqueInput
+}
+
+input TaskRunAttemptRelateToManyForCreateInput {
+ create: [TaskRunAttemptCreateInput!]
+ connect: [TaskRunAttemptWhereUniqueInput!]
+}
+
+type TaskRunAttempt {
+ id: ID!
+ number: Int
+ friendlyId: String
+ taskRun: TaskRun
+ status: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ startedAt: DateTime
+ completedAt: DateTime
+ error: JSON
+ output: String
+ outputType: String
+}
+
+input TaskRunAttemptWhereUniqueInput {
+ id: ID
+}
+
+input TaskRunAttemptWhereInput {
+ AND: [TaskRunAttemptWhereInput!]
+ OR: [TaskRunAttemptWhereInput!]
+ NOT: [TaskRunAttemptWhereInput!]
+ id: IDFilter
+ number: IntNullableFilter
+ friendlyId: StringFilter
+ taskRun: TaskRunWhereInput
+ status: StringNullableFilter
+ createdAt: DateTimeNullableFilter
+ updatedAt: DateTimeNullableFilter
+ startedAt: DateTimeNullableFilter
+ completedAt: DateTimeNullableFilter
+ output: StringFilter
+ outputType: StringFilter
+}
+
+input TaskRunAttemptOrderByInput {
+ id: OrderDirection
+ number: OrderDirection
+ friendlyId: OrderDirection
+ status: OrderDirection
+ createdAt: OrderDirection
+ updatedAt: OrderDirection
+ startedAt: OrderDirection
+ completedAt: OrderDirection
+ output: OrderDirection
+ outputType: OrderDirection
+}
+
+input TaskRunAttemptUpdateInput {
+ number: Int
+ friendlyId: String
+ taskRun: TaskRunRelateToOneForUpdateInput
+ status: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ startedAt: DateTime
+ completedAt: DateTime
+ error: JSON
+ output: String
+ outputType: String
+}
+
+input TaskRunRelateToOneForUpdateInput {
+ create: TaskRunCreateInput
+ connect: TaskRunWhereUniqueInput
+ disconnect: Boolean
+}
+
+input TaskRunAttemptUpdateArgs {
+ where: TaskRunAttemptWhereUniqueInput!
+ data: TaskRunAttemptUpdateInput!
+}
+
+input TaskRunAttemptCreateInput {
+ number: Int
+ friendlyId: String
+ taskRun: TaskRunRelateToOneForCreateInput
+ status: String
+ createdAt: DateTime
+ updatedAt: DateTime
+ startedAt: DateTime
+ completedAt: DateTime
+ error: JSON
+ output: String
+ outputType: String
+}
+
+input TaskRunRelateToOneForCreateInput {
+ create: TaskRunCreateInput
+ connect: TaskRunWhereUniqueInput
+}
+
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
@@ -79,6 +453,24 @@ type Mutation {
updateUsers(data: [UserUpdateArgs!]!): [User]
deleteUser(where: UserWhereUniqueInput!): User
deleteUsers(where: [UserWhereUniqueInput!]!): [User]
+ createProject(data: ProjectCreateInput!): Project
+ createProjects(data: [ProjectCreateInput!]!): [Project]
+ updateProject(where: ProjectWhereUniqueInput!, data: ProjectUpdateInput!): Project
+ updateProjects(data: [ProjectUpdateArgs!]!): [Project]
+ deleteProject(where: ProjectWhereUniqueInput!): Project
+ deleteProjects(where: [ProjectWhereUniqueInput!]!): [Project]
+ createTaskRun(data: TaskRunCreateInput!): TaskRun
+ createTaskRuns(data: [TaskRunCreateInput!]!): [TaskRun]
+ updateTaskRun(where: TaskRunWhereUniqueInput!, data: TaskRunUpdateInput!): TaskRun
+ updateTaskRuns(data: [TaskRunUpdateArgs!]!): [TaskRun]
+ deleteTaskRun(where: TaskRunWhereUniqueInput!): TaskRun
+ deleteTaskRuns(where: [TaskRunWhereUniqueInput!]!): [TaskRun]
+ createTaskRunAttempt(data: TaskRunAttemptCreateInput!): TaskRunAttempt
+ createTaskRunAttempts(data: [TaskRunAttemptCreateInput!]!): [TaskRunAttempt]
+ updateTaskRunAttempt(where: TaskRunAttemptWhereUniqueInput!, data: TaskRunAttemptUpdateInput!): TaskRunAttempt
+ updateTaskRunAttempts(data: [TaskRunAttemptUpdateArgs!]!): [TaskRunAttempt]
+ deleteTaskRunAttempt(where: TaskRunAttemptWhereUniqueInput!): TaskRunAttempt
+ deleteTaskRunAttempts(where: [TaskRunAttemptWhereUniqueInput!]!): [TaskRunAttempt]
endSession: Boolean!
authenticateUserWithPassword(name: String!, password: String!): UserAuthenticationWithPasswordResult
createInitialUser(data: CreateInitialUserInput!): UserAuthenticationWithPasswordSuccess!
@@ -101,9 +493,18 @@ input CreateInitialUserInput {
}
type Query {
- users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: UserWhereUniqueInput): [User!]
user(where: UserWhereUniqueInput!): User
+ users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: UserWhereUniqueInput): [User!]
usersCount(where: UserWhereInput! = {}): Int
+ projects(where: ProjectWhereInput! = {}, orderBy: [ProjectOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: ProjectWhereUniqueInput): [Project!]
+ project(where: ProjectWhereUniqueInput!): Project
+ projectsCount(where: ProjectWhereInput! = {}): Int
+ taskRuns(where: TaskRunWhereInput! = {}, orderBy: [TaskRunOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: TaskRunWhereUniqueInput): [TaskRun!]
+ taskRun(where: TaskRunWhereUniqueInput!): TaskRun
+ taskRunsCount(where: TaskRunWhereInput! = {}): Int
+ taskRunAttempts(where: TaskRunAttemptWhereInput! = {}, orderBy: [TaskRunAttemptOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: TaskRunAttemptWhereUniqueInput): [TaskRunAttempt!]
+ taskRunAttempt(where: TaskRunAttemptWhereUniqueInput!): TaskRunAttempt
+ taskRunAttemptsCount(where: TaskRunAttemptWhereInput! = {}): Int
keystone: KeystoneMeta!
authenticatedItem: AuthenticatedItem
}
@@ -135,6 +536,7 @@ type KeystoneAdminUIListMeta {
labelField: String!
fields: [KeystoneAdminUIFieldMeta!]!
groups: [KeystoneAdminUIFieldGroupMeta!]!
+ graphql: KeystoneAdminUIGraphQL!
initialSort: KeystoneAdminUISort
isHidden: Boolean!
isSingleton: Boolean!
@@ -207,6 +609,33 @@ type KeystoneAdminUIFieldGroupMeta {
fields: [KeystoneAdminUIFieldMeta!]!
}
+type KeystoneAdminUIGraphQL {
+ names: KeystoneAdminUIGraphQLNames!
+}
+
+type KeystoneAdminUIGraphQLNames {
+ outputTypeName: String!
+ whereInputName: String!
+ whereUniqueInputName: String!
+ createInputName: String!
+ createMutationName: String!
+ createManyMutationName: String!
+ relateToOneForCreateInputName: String!
+ relateToManyForCreateInputName: String!
+ itemQueryName: String!
+ listOrderName: String!
+ listQueryCountName: String!
+ listQueryName: String!
+ updateInputName: String!
+ updateMutationName: String!
+ updateManyInputName: String!
+ updateManyMutationName: String!
+ relateToOneForUpdateInputName: String!
+ relateToManyForUpdateInputName: String!
+ deleteMutationName: String!
+ deleteManyMutationName: String!
+}
+
type KeystoneAdminUISort {
field: String!
direction: KeystoneAdminUISortDirection!
diff --git a/examples/auth/schema.prisma b/examples/auth/schema.prisma
index 9ae26a3cb1c..f58555439ca 100644
--- a/examples/auth/schema.prisma
+++ b/examples/auth/schema.prisma
@@ -18,3 +18,54 @@ model User {
password String
isAdmin Boolean @default(false)
}
+
+model Project {
+ id String @id @default(cuid())
+ slug String @unique @default("")
+ name String @default("")
+ createdAt DateTime? @default(now())
+ updatedAt DateTime? @default(now()) @updatedAt
+ deletedAt DateTime?
+ taskRuns TaskRun[] @relation("TaskRun_project")
+}
+
+model TaskRun {
+ id String @id @default(cuid())
+ number Int? @default(0)
+ friendlyId String @default("")
+ status String? @default("pending")
+ taskIdentifier String @default("")
+ isTest Boolean @default(false)
+ payload String @default("")
+ payloadType String @default("application/json")
+ context String?
+ traceId String @default("")
+ spanId String @default("")
+ project Project? @relation("TaskRun_project", fields: [projectId], references: [id])
+ projectId String? @map("project")
+ createdAt DateTime? @default(now())
+ updatedAt DateTime? @default(now()) @updatedAt
+ attempts TaskRunAttempt[] @relation("TaskRunAttempt_taskRun")
+ startedAt DateTime?
+ completedAt DateTime?
+
+ @@index([projectId])
+}
+
+model TaskRunAttempt {
+ id String @id @default(cuid())
+ number Int? @default(0)
+ friendlyId String @default("")
+ taskRun TaskRun? @relation("TaskRunAttempt_taskRun", fields: [taskRunId], references: [id])
+ taskRunId String? @map("taskRun")
+ status String? @default("pending")
+ createdAt DateTime? @default(now())
+ updatedAt DateTime? @default(now()) @updatedAt
+ startedAt DateTime?
+ completedAt DateTime?
+ error String?
+ output String @default("")
+ outputType String @default("application/json")
+
+ @@index([taskRunId])
+}
diff --git a/examples/auth/schema.ts b/examples/auth/schema.ts
index 2879ee9f407..a657f6863c2 100644
--- a/examples/auth/schema.ts
+++ b/examples/auth/schema.ts
@@ -1,6 +1,6 @@
import { list } from '@keystone-6/core'
import { allowAll, denyAll } from '@keystone-6/core/access'
-import { text, checkbox, password } from '@keystone-6/core/fields'
+import { text, checkbox, password, timestamp, relationship, json, select, integer } from '@keystone-6/core/fields'
import type { Lists } from '.keystone/types'
// WARNING: this example is for demonstration purposes only
@@ -153,4 +153,76 @@ export const lists = {
}),
},
}),
+ Project: list({
+ access: allowAll,
+
+ fields: {
+ slug: text({ isIndexed: 'unique' }),
+ name: text({ validation: { isRequired: true } }),
+ createdAt: timestamp({ defaultValue: { kind: 'now' } }),
+ updatedAt: timestamp({ defaultValue: { kind: 'now' }, db: { updatedAt: true } }),
+ deletedAt: timestamp({}),
+ taskRuns: relationship({ ref: 'TaskRun.project', many: true }),
+ }
+ }),
+ TaskRun: list({
+ access: allowAll,
+
+ fields: {
+ number: integer({ defaultValue: 0 }),
+ friendlyId: text({}),
+ status: select({
+ options: [
+ { label: 'Pending', value: 'pending' },
+ { label: 'Executing', value: 'executing' },
+ { label: 'Paused', value: 'paused' },
+ { label: 'Canceled', value: 'canceled' },
+ { label: 'Interrupted', value: 'interrupted' },
+ { label: 'Completed Successfully', value: 'completed_successfully' },
+ { label: 'Completed With Errors', value: 'completed_with_errors' },
+ { label: 'System Failure', value: 'system_failure' },
+ { label: 'Crashed', value: 'crashed' }],
+ defaultValue: 'pending'
+ }),
+ taskIdentifier: text({}),
+ isTest: checkbox({ defaultValue: false }),
+ payload: text({}),
+ payloadType: text({ defaultValue: 'application/json' }),
+ context: json({}),
+ traceId: text({}),
+ spanId: text({}),
+ project: relationship({ ref: 'Project.taskRuns' }),
+ createdAt: timestamp({ defaultValue: { kind: 'now' } }),
+ updatedAt: timestamp({ defaultValue: { kind: 'now' }, db: { updatedAt: true } }),
+ attempts: relationship({ ref: 'TaskRunAttempt.taskRun', many: true }),
+ startedAt: timestamp({}),
+ completedAt: timestamp({}),
+ }
+ }),
+ TaskRunAttempt: list({
+ access: allowAll,
+
+ fields: {
+ number: integer({ defaultValue: 0 }),
+ friendlyId: text({}),
+ taskRun: relationship({ ref: 'TaskRun.attempts' }),
+ status: select({
+ options: [
+ { label: 'Pending', value: 'pending' },
+ { label: 'Executing', value: 'executing' },
+ { label: 'Paused', value: 'paused' },
+ { label: 'Failed', value: 'failed' },
+ { label: 'Canceled', value: 'canceled' },
+ { label: 'Completed', value: 'completed' }],
+ defaultValue: 'pending'
+ }),
+ createdAt: timestamp({ defaultValue: { kind: 'now' } }),
+ updatedAt: timestamp({ defaultValue: { kind: 'now' }, db: { updatedAt: true } }),
+ startedAt: timestamp({}),
+ completedAt: timestamp({}),
+ error: json({}),
+ output: text({}),
+ outputType: text({ defaultValue: 'application/json' }),
+ }
+ })
} satisfies Lists
diff --git a/examples/auth/tsconfig.json b/examples/auth/tsconfig.json
new file mode 100644
index 00000000000..ccb2ed95d83
--- /dev/null
+++ b/examples/auth/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "incremental": true,
+ "module": "esnext",
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/examples/cloudinary/package.json b/examples/cloudinary/package.json
index 657c808cba4..3e1ade1e9a8 100644
--- a/examples/cloudinary/package.json
+++ b/examples/cloudinary/package.json
@@ -1,6 +1,6 @@
{
"name": "@keystone-6/example-cloudinary",
- "version": "0.0.2",
+ "version": null,
"private": true,
"license": "MIT",
"scripts": {
@@ -10,14 +10,14 @@
"postinstall": "keystone postinstall"
},
"dependencies": {
- "@keystone-6/cloudinary": "workspace:^",
- "@keystone-6/auth": "workspace:^",
- "@keystone-6/core": "workspace:^",
- "@prisma/client": "catalog:",
+ "@keystone-6/cloudinary": "^8.0.0",
+ "@keystone-6/auth": "^8.0.0",
+ "@keystone-6/core": "^6.2.0",
+ "@prisma/client": "5.17.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
- "prisma": "catalog:",
- "typescript": "catalog:"
+ "prisma": "5.17.0",
+ "typescript": "^5.5.0"
}
}
diff --git a/examples/cloudinary/schema.graphql b/examples/cloudinary/schema.graphql
index 3f72e019b47..f22a4835046 100644
--- a/examples/cloudinary/schema.graphql
+++ b/examples/cloudinary/schema.graphql
@@ -154,8 +154,8 @@ type Mutation {
}
type Query {
- posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
post(where: PostWhereUniqueInput!): Post
+ posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
postsCount(where: PostWhereInput! = {}): Int
keystone: KeystoneMeta!
}
@@ -185,6 +185,7 @@ type KeystoneAdminUIListMeta {
labelField: String!
fields: [KeystoneAdminUIFieldMeta!]!
groups: [KeystoneAdminUIFieldGroupMeta!]!
+ graphql: KeystoneAdminUIGraphQL!
initialSort: KeystoneAdminUISort
isHidden: Boolean!
isSingleton: Boolean!
@@ -257,6 +258,33 @@ type KeystoneAdminUIFieldGroupMeta {
fields: [KeystoneAdminUIFieldMeta!]!
}
+type KeystoneAdminUIGraphQL {
+ names: KeystoneAdminUIGraphQLNames!
+}
+
+type KeystoneAdminUIGraphQLNames {
+ outputTypeName: String!
+ whereInputName: String!
+ whereUniqueInputName: String!
+ createInputName: String!
+ createMutationName: String!
+ createManyMutationName: String!
+ relateToOneForCreateInputName: String!
+ relateToManyForCreateInputName: String!
+ itemQueryName: String!
+ listOrderName: String!
+ listQueryCountName: String!
+ listQueryName: String!
+ updateInputName: String!
+ updateMutationName: String!
+ updateManyInputName: String!
+ updateManyMutationName: String!
+ relateToOneForUpdateInputName: String!
+ relateToManyForUpdateInputName: String!
+ deleteMutationName: String!
+ deleteManyMutationName: String!
+}
+
type KeystoneAdminUISort {
field: String!
direction: KeystoneAdminUISortDirection!
diff --git a/examples/custom-admin-ui-logo/app/(admin)/.admin/index.tsx b/examples/custom-admin-ui-logo/app/(admin)/.admin/index.tsx
new file mode 100644
index 00000000000..5dd11446daf
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/.admin/index.tsx
@@ -0,0 +1,18 @@
+/* eslint-disable */
+import * as view0 from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view'
+import * as view1 from '@keystone-6/core/fields/types/text/views'
+import * as view2 from '@keystone-6/core/fields/types/select/views'
+import * as view3 from '@keystone-6/core/fields/types/checkbox/views'
+import * as view4 from '@keystone-6/core/fields/types/relationship/views'
+import * as view5 from '@keystone-6/core/fields/types/timestamp/views'
+
+import * as adminConfig from '../config'
+
+export const config = {
+ lazyMetadataQuery: {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"keystone","loc":{"start":22,"end":30}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminMeta","loc":{"start":39,"end":48}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lists","loc":{"start":59,"end":64}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key","loc":{"start":77,"end":80}},"arguments":[],"directives":[],"loc":{"start":77,"end":80}},{"kind":"Field","name":{"kind":"Name","value":"isHidden","loc":{"start":91,"end":99}},"arguments":[],"directives":[],"loc":{"start":91,"end":99}},{"kind":"Field","name":{"kind":"Name","value":"fields","loc":{"start":110,"end":116}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path","loc":{"start":131,"end":135}},"arguments":[],"directives":[],"loc":{"start":131,"end":135}},{"kind":"Field","name":{"kind":"Name","value":"createView","loc":{"start":148,"end":158}},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fieldMode","loc":{"start":175,"end":184}},"arguments":[],"directives":[],"loc":{"start":175,"end":184}}],"loc":{"start":159,"end":198}},"loc":{"start":148,"end":198}}],"loc":{"start":117,"end":210}},"loc":{"start":110,"end":210}}],"loc":{"start":65,"end":220}},"loc":{"start":59,"end":220}}],"loc":{"start":49,"end":228}},"loc":{"start":39,"end":228}}],"loc":{"start":31,"end":234}},"loc":{"start":22,"end":234}}]}}]},
+ fieldViews: [view0,view1,view2,view3,view4,view5],
+ adminMetaHash: '1mrsjib',
+ adminConfig,
+ apiPath: '/api/graphql',
+ adminPath: '',
+};
diff --git a/examples/custom-admin-ui-logo/app/(admin)/[listKey]/[id]/page.tsx b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/[id]/page.tsx
new file mode 100644
index 00000000000..c5d7ea2be62
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/[id]/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { ItemPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage'
+
+export default ItemPage
diff --git a/examples/custom-admin-ui-logo/app/(admin)/[listKey]/create/page.tsx b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/create/page.tsx
new file mode 100644
index 00000000000..d6042acaa96
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/create/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { CreateItemPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage'
+
+export default CreateItemPage
diff --git a/examples/custom-admin-ui-logo/app/(admin)/[listKey]/page.tsx b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/page.tsx
new file mode 100644
index 00000000000..f6e75f8cfab
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/[listKey]/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { ListPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage'
+
+export default ListPage
diff --git a/examples/custom-admin-ui-logo/app/(admin)/config.tsx b/examples/custom-admin-ui-logo/app/(admin)/config.tsx
new file mode 100644
index 00000000000..f2b986e29ca
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/config.tsx
@@ -0,0 +1 @@
+export { components } from '../config'
\ No newline at end of file
diff --git a/examples/custom-admin-ui-logo/app/(admin)/layout.tsx b/examples/custom-admin-ui-logo/app/(admin)/layout.tsx
new file mode 100644
index 00000000000..abb5a0f3b2c
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/layout.tsx
@@ -0,0 +1,16 @@
+'use client'
+import { Layout } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App'
+import { config } from './.admin'
+
+
+export default function AdminLayout ({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
\ No newline at end of file
diff --git a/examples/custom-admin-ui-logo/app/(admin)/no-access/page.tsx b/examples/custom-admin-ui-logo/app/(admin)/no-access/page.tsx
new file mode 100644
index 00000000000..70877231fee
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/no-access/page.tsx
@@ -0,0 +1,4 @@
+'use client'
+import { getNoAccessPage } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage'
+
+export default getNoAccessPage({ sessionsEnabled: false })
diff --git a/examples/custom-admin-ui-logo/app/(admin)/page.tsx b/examples/custom-admin-ui-logo/app/(admin)/page.tsx
new file mode 100644
index 00000000000..5c268390b0f
--- /dev/null
+++ b/examples/custom-admin-ui-logo/app/(admin)/page.tsx
@@ -0,0 +1,2 @@
+'use client'
+export { HomePage as default } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage'
diff --git a/examples/custom-admin-ui-logo/admin/components/CustomLogo.tsx b/examples/custom-admin-ui-logo/app/config/components/CustomLogo.tsx
similarity index 94%
rename from examples/custom-admin-ui-logo/admin/components/CustomLogo.tsx
rename to examples/custom-admin-ui-logo/app/config/components/CustomLogo.tsx
index 1a7994f2b29..f8ce3fc3b7a 100644
--- a/examples/custom-admin-ui-logo/admin/components/CustomLogo.tsx
+++ b/examples/custom-admin-ui-logo/app/config/components/CustomLogo.tsx
@@ -3,7 +3,7 @@
import Link from 'next/link'
import { jsx, H3 } from '@keystone-ui/core'
-export const CustomLogo = () => {
+export function CustomLogo () {
return (
+ {children}
+