Components and utils to extend Sanity
@tinloof/sanity-kit/navigator
Pages navigator to use in the new Presentation framework as "unstable_navigator".
This plugin is in beta
Required dependencies and minimum versions
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sanity": "^3.23.1"
"@sanity/overlays": "^2.3.0",
"@sanity/react-loader": "^1.6.4",
"@sanity/vision": "^3.23.1",
Note:
The package is meant to be used with projects that use the new fetching system of loaders and overlays that is compatinble with presentation.
Read more here →
This is the official Next.js starter from Sanity that uses this new updated system for reference
Getting started
Install
npm i @tinloof/sanity-kit
Wrap your Studio with NavigatorProvider
'use client'
import { NavigatorProvider } from '@tinloof/sanity-kit/navigator'
import { NextStudio } from 'next-sanity/studio'
export default function Studio() {
return (
<NavigatorProvider>
<NextStudio config={config} />
</NavigatorProvider>
)
}
In your Sanity folder, wherever you keep your Sanity related config and files, create a React component called PagesNavigator
(you can actually call it whatever you prefer, this is simply a suggested name becasue of how will be shown later in these instructions)
Start by importing the Navigator and all the needed hooks from the plugin
import Navigator, {
ThemeProvider,
useNavigator,
useSanityFetch,
useUpdateTree
} from '@tinloof/sanity-kit/navigator'
At this point you will need to import a query to fetch your pages and an array of locales (if you use multi locales, it is not required).
import { pagesRoutesQuery } from '@/sanity/lib/queries'
import locales from '@/sanity/schemas/locales'
To work with the Navigator, pages need to have these fields as required
_id: string →
_type: string →
_updatedAt: string →
_createdAt: string →
title: string →
routePath: string →
While the locales array should look like this (if you are using locales); it goes without sayng that these locales should be reflecting and mirroring your locales in Sanity
[
{ title: 'English', value: 'en' },
{ title: 'French', value: 'fr' },
{ title: 'Spanish', value: 'es' },
]
Once these are setup, you can start composing your PagesNavigator
export default function PagesNavigator() {
const { items, locale } = useNavigator()
const availableLocales = locales
const [data, loading] = useSanityFetch({
query: pagesRoutesQuery,
variables: {
types: ['page', 'home'],
locale: !!locale ? locale : availableLocales[0].value,
},
})
useUpdateTree(data)
function renderPages() {
if(items.length) {
return <Navigator.List items={items} />
} else {
<Navigator.EmptySearchResults />
}
}
return (
<ThemeProvider>
<Navigator.Header title="Pages Navigator">
<Navigator.SearchBox />
{availableLocales.length ? (
<Navigator.LocaleSelect locales={availableLocales} />
) : null}
</Navigator.Header>
{loading ? <Navigator.SkeletonListItems items={40} /> : null}
{renderPages()}
</ThemeProvider>
)
}
At this point all is left is to go to your sanity.config.ts
and add the PagesNavigator as unstable_component
in your presentationTool
plugin
import PagesNavigator from "./sanity/components/PagesNavigator";
export default defineConfig({
plugins: [
presentationTool({
previewUrl: {
origin:
typeof location === "undefined"
? "http://localhost:3000"
: location.origin,
draftMode: {
enable: "/api/draft",
},
},
components: {
unstable_navigator: {
component: PagesNavigator,
minWidth: 360,
maxWidth: 480,
},
},
}),
],
});
Once all is in plance you should be able to see your pages and navigate through them easily.
@tinloof/sanity-kit/utils
Getting started
Install
npm i @tinloof/sanity-kit
RoutePathField
RoutePathField integrates with the navigator to simplify the task of creating route paths or slugs with nested segments/folders. It generates and appends the segments/folder structure of the URL, leaving only the last part of the slug (the actual last segment of the page) for the editor. For example, if the slug of a page is "/content/nested/page", the "content/nested" part will be an editable button that displays the current path up to the actual last segment of the page.
It takes 3 props, including the value coming from Sanity:
value: Spread the value obtained from component.field
(refer to the example below).
siteDomain: The domain of the production URL, used for display purposes.
defaultLocale: The default locale for the site. It is used to exclude the default locale from the URL and only include the locales that require recognition.
In your Sanity schema, import the RoutePathField custom field:
import { RoutePathField } from '@tinloof/sanity-kit/utils'
defineField({
type: 'slug',
name: 'routePath',
title: 'Route path',
components: {
field: (value) =>
RoutePathField({
...value,
siteDomain: 'https://example.com',
defaultLocale: "en",
}),
},
validation: (rule) => rule.required(),
}),
Blocks Picker
Blocks wizard is an abstraction in both UI and logic to build pages with a "blocks" mindset. The major abstraction in the UI is a modal picker to preview and select blocks visually.
The logic is abstracted using blockSchema
, which lets you simply create your blocks wrapping this utility around it to make it work automatically, and BlockArrayField
, which is the component to use in your page body field, also referred to in the following examples as blocksBody
.
How to use it:
Step 1: Create your first block using the blockSchema
utility. In this example we create a basic Hero block and we create a file in theblocks folder to export all blocks that we will use in the next steps:
import { StarIcon } from '@sanity/icons'
import { blockSchema } from '@tinloof/sanity-kit/utils'
export const blockHero = blockSchema({
name: 'block.hero',
title: 'Hero',
type: 'object',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
validation: (Rule) => Rule.required(),
},
],
})
We use the name convention block.*
for consistency.
import { hero } from './hero'
const blocks = [
hero,
]
export default blocks
Step 2: create a body
object to render all your blocks in a page
import { BlockArrayField } from '@tinloof/sanity-kit/utils'
import { defineType } from 'sanity'
import blocks from '../blocks'
export const body = defineType({
name: 'body',
title: 'Content',
type: 'array',
of: blocks.map((block) => ({
type: block.name,
})),
components: {
input: BlockArrayField,
},
})
Step 3: Add your newly created schemas to Sanity config
export default defineConfig({
basePath: studioUrl,
projectId: projectId || '',
dataset: dataset || '',
title,
schema: {
types: [
body,
hero,
],
},
plugins:
})
Step 4: use your body object in any page you want:
export default defineType({
type: 'document',
name: 'page',
title: 'Page',
icon: DocumentIcon,
fields: [
defineField({
name: 'body',
title: 'Content / body of the page',
type: 'body',
}),
],
})