navbar schema
This commit is contained in:
1
template/apps/client/src/routes/second/+page.svelte
Normal file
1
template/apps/client/src/routes/second/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<p>Second page</p>
|
||||
@@ -1 +0,0 @@
|
||||
<p>Test</p>
|
||||
@@ -20,10 +20,10 @@
|
||||
"@repo/sanity-connection": "workspace:*",
|
||||
"@repo/ui": "workspace:*",
|
||||
"@sanity/document-internationalization": "^4.0.0",
|
||||
"@sanity/vision": "^4.3.0",
|
||||
"@sanity/vision": "^4.5.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"sanity": "^4.3.0",
|
||||
"sanity": "^4.5.0",
|
||||
"sanity-plugin-link-field": "^1.4.0",
|
||||
"sanity-plugin-seo": "^1.3.3",
|
||||
"sanity-plugin-simpler-color-input": "^3.1.1",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {defineConfig} from 'sanity'
|
||||
import {structureTool} from 'sanity/structure'
|
||||
import {ClipboardIcon, HomeIcon, WrenchIcon} from '@sanity/icons'
|
||||
import {ClipboardIcon, HomeIcon, MenuIcon, WrenchIcon} from '@sanity/icons'
|
||||
import {schemaTypes} from './schemaTypes'
|
||||
import {presentationTool} from 'sanity/presentation'
|
||||
import {linkField} from 'sanity-plugin-link-field'
|
||||
import {seoMetaFields} from 'sanity-plugin-seo'
|
||||
import {simplerColorInput} from 'sanity-plugin-simpler-color-input'
|
||||
import {createColorList} from './lib/colorUtils'
|
||||
import { sanityConnection } from '@repo/sanity-connection'
|
||||
import {sanityConnection} from '@repo/sanity-connection'
|
||||
import Logo from './components/logo'
|
||||
|
||||
export default defineConfig({
|
||||
@@ -23,6 +23,15 @@ export default defineConfig({
|
||||
S.list()
|
||||
.title('Content')
|
||||
.items([
|
||||
S.listItem()
|
||||
.title('Settings')
|
||||
.icon(WrenchIcon)
|
||||
.child(S.document().schemaType('settings').documentId('settings').title('Settings')),
|
||||
S.listItem()
|
||||
.title('Navbar')
|
||||
.icon(MenuIcon)
|
||||
.child(S.document().schemaType('navbar').documentId('navbar').title('Navbar')),
|
||||
S.divider(),
|
||||
S.listItem()
|
||||
.title('Landing Page')
|
||||
.icon(HomeIcon)
|
||||
@@ -32,11 +41,6 @@ export default defineConfig({
|
||||
.title('Custom pages')
|
||||
.icon(ClipboardIcon)
|
||||
.child(S.documentTypeList('custom').title('Content')),
|
||||
S.divider(),
|
||||
S.listItem()
|
||||
.title('Settings')
|
||||
.icon(WrenchIcon)
|
||||
.child(S.document().schemaType('settings').documentId('settings').title('Settings')),
|
||||
]),
|
||||
}),
|
||||
// @ts-ignore
|
||||
|
||||
@@ -4,11 +4,13 @@ import settings from "./settings";
|
||||
import * as objects from './objects'
|
||||
import * as sections from './sections'
|
||||
import * as pages from './pages'
|
||||
import navbar from "./navbar";
|
||||
|
||||
export const schemaTypes = [
|
||||
blockContent,
|
||||
settings,
|
||||
|
||||
navbar,
|
||||
|
||||
...Object.values(objects),
|
||||
...Object.values(sections),
|
||||
...Object.values(pages),
|
||||
|
||||
250
template/apps/studio/schemaTypes/navbar.ts
Normal file
250
template/apps/studio/schemaTypes/navbar.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { LinkIcon, MenuIcon } from "@sanity/icons"
|
||||
import { defineField, defineType, type Rule, type StringRule } from "sanity"
|
||||
import { requiredLinkField } from "sanity-plugin-link-field"
|
||||
|
||||
export default defineType({
|
||||
name: 'navbar',
|
||||
title: 'Navigation',
|
||||
type: 'document',
|
||||
icon: MenuIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'title',
|
||||
title: 'Titel',
|
||||
type: 'string',
|
||||
initialValue: 'Navigation',
|
||||
readOnly: true,
|
||||
hidden: true,
|
||||
}),
|
||||
defineField({
|
||||
name: 'links',
|
||||
title: 'Links',
|
||||
icon: LinkIcon,
|
||||
type: 'array',
|
||||
initialValue: [
|
||||
{
|
||||
text: 'Home',
|
||||
link: {
|
||||
type: 'url',
|
||||
value: '/',
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Second Page',
|
||||
link: {
|
||||
type: 'url',
|
||||
value: '/second',
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Blog',
|
||||
link: {
|
||||
type: 'url',
|
||||
value: '/blog',
|
||||
},
|
||||
sublinks: [
|
||||
{
|
||||
type: 'auto',
|
||||
pageType: 'blog',
|
||||
autoTitle: 'Latest Blog Posts'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
of: [
|
||||
{
|
||||
type: 'object',
|
||||
title: 'Link',
|
||||
icon: LinkIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'text',
|
||||
title: 'Text',
|
||||
type: 'string',
|
||||
validation: (Rule: StringRule) => Rule.required().error('Text is required'),
|
||||
}),
|
||||
defineField({
|
||||
name: 'link',
|
||||
title: 'Link',
|
||||
type: 'link',
|
||||
validation: (rule: Rule) => rule.custom((field: Rule) => requiredLinkField(field))
|
||||
}),
|
||||
defineField({
|
||||
name: 'sublinks',
|
||||
title: 'Sublinks',
|
||||
type: 'array',
|
||||
validation: (Rule) => Rule.max(5).error('Maximum 5 sublinks allowed'),
|
||||
of: [
|
||||
{
|
||||
type: 'object',
|
||||
title: 'Sublink',
|
||||
icon: LinkIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'type',
|
||||
title: 'Link Type',
|
||||
type: 'string',
|
||||
initialValue: 'auto',
|
||||
options: {
|
||||
list: [
|
||||
{
|
||||
title: 'Last Pages',
|
||||
value: 'auto'
|
||||
},
|
||||
{
|
||||
title: 'Pages by Tag',
|
||||
value: 'tag'
|
||||
},
|
||||
{
|
||||
title: 'Manual Link',
|
||||
value: 'manual'
|
||||
},
|
||||
],
|
||||
layout: 'radio',
|
||||
},
|
||||
validation: (Rule: StringRule) => Rule.required(),
|
||||
}),
|
||||
defineField({
|
||||
name: 'pageType',
|
||||
title: 'Page Type',
|
||||
type: 'string',
|
||||
initialValue: 'custom',
|
||||
description: 'Automatically displays the 5 most recently published pages from the selected type',
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Custom Pages', value: 'custom' },
|
||||
{ title: 'Blog Posts', value: 'blog' },
|
||||
],
|
||||
},
|
||||
hidden: ({ parent }) => parent?.type !== 'auto',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'auto' && !value) {
|
||||
return 'A page type must be selected'
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
defineField({
|
||||
name: 'text',
|
||||
title: 'Text',
|
||||
type: 'string',
|
||||
hidden: ({ parent }) => parent?.type !== 'manual',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'manual' && !value) {
|
||||
return 'Text is required for manual links'
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
defineField({
|
||||
name: 'link',
|
||||
title: 'Link',
|
||||
type: 'link',
|
||||
hidden: ({ parent }) => parent?.type !== 'manual',
|
||||
validation: (rule: Rule) =>
|
||||
rule.custom((field, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'manual') {
|
||||
return requiredLinkField(field)
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
defineField({
|
||||
name: 'tagFilter',
|
||||
title: 'Tag Filter',
|
||||
type: 'reference',
|
||||
to: [{ type: 'tag' }],
|
||||
description: 'Select a tag to filter pages by. The last 5 published pages with this tag will be shown.',
|
||||
hidden: ({ parent }) => parent?.type !== 'tag',
|
||||
validation: (Rule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'tag' && (!value)) {
|
||||
return 'A tag is required when using tag-based filtering'
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
defineField({
|
||||
name: 'tagPageType',
|
||||
title: 'Page Type for Tag Filter',
|
||||
type: 'string',
|
||||
description: 'Select which type of pages to search for the tag',
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Custom Pages', value: 'custom' },
|
||||
{ title: 'Blog Posts', value: 'blog' },
|
||||
],
|
||||
},
|
||||
hidden: ({ parent }) => parent?.type !== 'tag',
|
||||
initialValue: 'custom',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'tag' && !value) {
|
||||
return 'A page type must be selected for tag filtering'
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'text',
|
||||
type: 'type',
|
||||
tagTitles: 'tagTitles',
|
||||
autoTitle: 'autoTitle',
|
||||
pageType: 'pageType',
|
||||
tagPageType: 'tagPageType',
|
||||
},
|
||||
prepare({ title, type, tagTitles, autoTitle, pageType, tagPageType }) {
|
||||
let displayTitle = title
|
||||
let subtitle = ''
|
||||
|
||||
const formatPageType = (pageType: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
home: 'Home',
|
||||
custom: 'Custom',
|
||||
blog: 'Blog'
|
||||
}
|
||||
return typeMap[pageType] || pageType.charAt(0).toUpperCase() + pageType.slice(1)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'auto':
|
||||
displayTitle = autoTitle || 'Last Pages'
|
||||
subtitle = `Auto: Latest 5 from ${formatPageType(pageType)}`
|
||||
break
|
||||
case 'tag':
|
||||
const tagNames = tagTitles?.filter(Boolean).join(', ') || 'No tags'
|
||||
displayTitle = autoTitle || `Pages tagged: ${tagNames}`
|
||||
subtitle = `Tag: "${tagNames}" in ${formatPageType(tagPageType)}`
|
||||
break
|
||||
case 'manual':
|
||||
subtitle = 'Manual link'
|
||||
break
|
||||
default:
|
||||
displayTitle = 'Unconfigured link'
|
||||
}
|
||||
|
||||
return {
|
||||
title: displayTitle || 'Untitled Sublink',
|
||||
subtitle,
|
||||
media: LinkIcon,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as faq } from './faq'
|
||||
export { default as imageWithAlt } from './imageWithAlt'
|
||||
export { default as button } from './button'
|
||||
export { default as tag } from './tag'
|
||||
|
||||
46
template/apps/studio/schemaTypes/objects/tag.ts
Normal file
46
template/apps/studio/schemaTypes/objects/tag.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { TagIcon } from '@sanity/icons'
|
||||
import { defineField, defineType, type StringRule } from 'sanity'
|
||||
|
||||
export default defineType({
|
||||
name: 'tag',
|
||||
title: 'Tag',
|
||||
type: 'document',
|
||||
icon: TagIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'title',
|
||||
title: 'Title',
|
||||
type: 'string',
|
||||
validation: (Rule: StringRule) => Rule.required().error('Tag title is required'),
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
title: 'Slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: 'title',
|
||||
maxLength: 96,
|
||||
},
|
||||
validation: (Rule) => Rule.required(),
|
||||
}),
|
||||
defineField({
|
||||
name: 'description',
|
||||
title: 'Description',
|
||||
type: 'text',
|
||||
rows: 3,
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
},
|
||||
prepare({ title, description }) {
|
||||
return {
|
||||
title: title || 'Untitled Tag',
|
||||
subtitle: description || 'No description',
|
||||
media: TagIcon,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
78
template/apps/studio/schemaTypes/pages/blog.ts
Normal file
78
template/apps/studio/schemaTypes/pages/blog.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { DocumentIcon } from '@sanity/icons'
|
||||
import { defineField, defineType, type SlugRule, type StringRule } from 'sanity'
|
||||
|
||||
export default defineType({
|
||||
name: 'blog',
|
||||
title: 'Blog Post',
|
||||
type: 'document',
|
||||
icon: DocumentIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'title',
|
||||
title: 'Title',
|
||||
type: 'string',
|
||||
validation: (Rule: StringRule) => Rule.required().error('Title is required')
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
title: 'Slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: 'title',
|
||||
maxLength: 96,
|
||||
},
|
||||
validation: (Rule: SlugRule) => Rule.required(),
|
||||
}),
|
||||
defineField({
|
||||
name: 'author',
|
||||
title: 'Author',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'publishedAt',
|
||||
title: 'Published at',
|
||||
type: 'datetime',
|
||||
}),
|
||||
defineField({
|
||||
name: 'tags',
|
||||
title: 'Tags',
|
||||
type: 'array',
|
||||
of: [{ type: 'string' }],
|
||||
description: 'Add tags to categorize this post. Tags can be used to filter and group related content in navigation menus.',
|
||||
options: {
|
||||
layout: 'tags',
|
||||
},
|
||||
}),
|
||||
defineField({
|
||||
name: 'excerpt',
|
||||
title: 'Excerpt',
|
||||
type: 'text',
|
||||
rows: 4,
|
||||
}),
|
||||
defineField({
|
||||
name: 'mainImage',
|
||||
title: 'Main image',
|
||||
type: 'imageWithAlt',
|
||||
}),
|
||||
defineField({
|
||||
title: 'Content',
|
||||
name: 'body',
|
||||
type: 'blockContent',
|
||||
}),
|
||||
],
|
||||
|
||||
preview: {
|
||||
select: {
|
||||
title: 'title',
|
||||
author: 'author',
|
||||
media: 'mainImage',
|
||||
},
|
||||
prepare({ title, author, media }) {
|
||||
return {
|
||||
title: title || 'Untitled',
|
||||
subtitle: author && `by ${author}`,
|
||||
media,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -21,6 +21,22 @@ export default defineType({
|
||||
},
|
||||
validation: (Rule: SlugRule) => Rule.required(),
|
||||
}),
|
||||
defineField({
|
||||
name: 'tags',
|
||||
title: 'Tags',
|
||||
type: 'array',
|
||||
of: [{ type: 'string' }],
|
||||
description: 'Add tags to categorize this page. Tags can be used to filter and group related content in navigation menus.',
|
||||
options: {
|
||||
layout: 'tags',
|
||||
},
|
||||
}),
|
||||
defineField({
|
||||
name: 'publishedAt',
|
||||
title: 'Published At',
|
||||
type: 'datetime',
|
||||
initialValue: () => new Date().toISOString(),
|
||||
}),
|
||||
defineField({
|
||||
title: 'Content',
|
||||
name: 'body',
|
||||
|
||||
@@ -21,6 +21,13 @@ export default defineType({
|
||||
validation: (Rule) => Rule.required(),
|
||||
group: 'header',
|
||||
}),
|
||||
defineField({
|
||||
name: 'publishedAt',
|
||||
title: 'Published At',
|
||||
type: 'datetime',
|
||||
initialValue: () => new Date().toISOString(),
|
||||
group: 'header',
|
||||
}),
|
||||
|
||||
// Page sections
|
||||
defineField({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Page types
|
||||
export { default as homePage } from './home'
|
||||
export { default as custom } from "./custom";
|
||||
export { default as blog } from "./blog";
|
||||
|
||||
Reference in New Issue
Block a user