Skip to content

Commit

Permalink
add TabbedContent
Browse files Browse the repository at this point in the history
  • Loading branch information
Mitchell Christ committed Sep 25, 2024
1 parent f33e307 commit c402a68
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sanitypress",
"version": "4.7.6",
"version": "4.7.7",
"description": "A Next.js + Sanity.io Starter Template",
"author": "nuotsu <mitchell@nuotsu.dev> (https://nuotsu.dev)",
"license": "ISC",
Expand Down
1 change: 1 addition & 0 deletions sanity/schemas/documents/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default defineType({
{ type: 'richtext-module' },
{ type: 'stat-list' },
{ type: 'step-list' },
{ type: 'tabbed-content' },
{ type: 'testimonial-list' },
{ type: 'testimonial.featured' },
],
Expand Down
2 changes: 2 additions & 0 deletions sanity/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import pricingList from './modules/pricing-list'
import richtextModule from './modules/richtext-module'
import statList from './modules/stat-list'
import stepList from './modules/step-list'
import tabbedContent from './modules/tabbed-content'
import testimonialFeatured from './modules/testimonial.featured'
import testimonialList from './modules/testimonial-list'

Expand Down Expand Up @@ -78,6 +79,7 @@ export const schemaTypes = [
richtextModule,
statList,
stepList,
tabbedContent,
testimonialFeatured,
testimonialList,
]
104 changes: 104 additions & 0 deletions sanity/schemas/modules/tabbed-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { defineArrayMember, defineField, defineType } from 'sanity'
import { PiTabs } from 'react-icons/pi'
import { getBlockText } from '@sanity/src/utils'

export default defineType({
name: 'tabbed-content',
title: 'Tabbed content',
icon: PiTabs,
type: 'object',
fields: [
defineField({
name: 'pretitle',
type: 'string',
}),
defineField({
name: 'intro',
type: 'array',
of: [{ type: 'block' }],
}),
defineField({
name: 'tabs',
title: 'Tabs',
type: 'array',
of: [
defineArrayMember({
type: 'object',
fields: [
defineField({
name: 'label',
type: 'string',
}),
defineField({
name: 'pretitle',
type: 'string',
}),
defineField({
name: 'content',
type: 'array',
of: [{ type: 'block' }],
}),
defineField({
name: 'ctas',
title: 'Call-to-actions',
type: 'array',
of: [{ type: 'cta' }],
}),
defineField({
name: 'image',
type: 'image',
fields: [
defineField({
name: 'alt',
type: 'string',
}),
defineField({
name: 'onRight',
type: 'boolean',
description: 'Display to the right of the content on desktop',
initialValue: false,
}),
defineField({
name: 'onBottom',
type: 'boolean',
description: 'Display below the content on mobile',
initialValue: false,
}),
defineField({
name: 'loading',
type: 'string',
options: {
list: ['lazy', 'eager'],
layout: 'radio',
},
initialValue: 'lazy',
}),
],
}),
],
preview: {
select: {
content: 'content',
label: 'label',
image: 'image',
},
prepare: ({ content, label, image }) => ({
title: getBlockText(content),
subtitle: label,
media: image,
}),
},
}),
],
}),
],
preview: {
select: {
intro: 'intro',
},
prepare: ({ intro }) => ({
title: getBlockText(intro),
subtitle: 'Tabbed content',
}),
},
})
29 changes: 29 additions & 0 deletions src/ui/modules/TabbedContent/TabList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import { tabbedContentStore } from './store'
import TabbedContent from '.'
import { cn } from '@/lib/utils'
import type { ComponentProps } from 'react'

export default function TabList({
tabs,
}: ComponentProps<typeof TabbedContent>) {
const { active, setActive } = tabbedContentStore()

return (
<nav className="max-md:full-bleed flex overflow-x-auto border-b border-accent">
{tabs?.map((tab, key) => (
<button
className={cn(
'shrink-0 grow basis-[min(150px,80vw)] rounded-t p-2',
key === active && 'action rounded-b-none',
)}
key={key}
onClick={() => setActive(key)}
>
{tab.label}
</button>
))}
</nav>
)
}
13 changes: 13 additions & 0 deletions src/ui/modules/TabbedContent/Wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client'

import { tabbedContentStore } from './store'
import type { ComponentProps } from 'react'

export default function Wrapper({
index,
...props
}: { index: number } & ComponentProps<'article'>) {
const { active } = tabbedContentStore()

return <article {...props} hidden={index !== active} />
}
68 changes: 68 additions & 0 deletions src/ui/modules/TabbedContent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Pretitle from '@/ui/Pretitle'
import { PortableText } from 'next-sanity'
import TabList from './TabList'
import Wrapper from './Wrapper'
import CTAList from '@/ui/CTAList'
import Img from '@/ui/Img'
import { cn } from '@/lib/utils'

export default function TabbedContent({
pretitle,
intro,
tabs,
}: Partial<{
pretitle: string
intro: any
tabs: Partial<{
label: string
pretitle: string
content: any
ctas: Sanity.CTA[]
image: Sanity.Image & {
onRight: boolean
onBottom: boolean
}
}>[]
}>) {
return (
<section className="section space-y-8">
{(pretitle || intro) && (
<header className="richtext text-center">
<Pretitle>{pretitle}</Pretitle>
<PortableText value={intro} />
</header>
)}

<TabList tabs={tabs} />

{tabs?.map((tab, index) => (
<Wrapper
className="grid items-center gap-8 *:mx-auto *:max-w-lg md:grid-cols-2 md:gap-x-12"
index={index}
key={index}
>
<figure
className={cn(
'anim-fade-to-r',
tab.image?.onRight && 'md:anim-fade-to-l md:order-last',
tab.image?.onBottom && 'max-md:order-last',
)}
>
<Img image={tab.image} />
</figure>

<div
className={cn(
'richtext anim-fade-to-r w-full',
!tab.image?.onRight && 'md:anim-fade-to-l',
)}
>
<Pretitle>{tab.pretitle}</Pretitle>
<PortableText value={tab.content} />
<CTAList ctas={tab.ctas} />
</div>
</Wrapper>
))}
</section>
)
}
9 changes: 9 additions & 0 deletions src/ui/modules/TabbedContent/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { create } from 'zustand'

export const tabbedContentStore = create<{
active: number
setActive: (tab: number) => void
}>((set) => ({
active: 0,
setActive: (tab) => set({ active: tab }),
}))
3 changes: 3 additions & 0 deletions src/ui/modules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PricingList from './PricingList'
import RichtextModule from './RichtextModule'
import StatList from './StatList'
import StepList from './StepList'
import TabbedContent from './TabbedContent'
import TestimonialList from './TestimonialList'
import TestimonialFeatured from './TestimonialFeatured'

Expand Down Expand Up @@ -71,6 +72,8 @@ export default function Modules({
return <StatList {...module} key={module._key} />
case 'step-list':
return <StepList {...module} key={module._key} />
case 'tabbed-content':
return <TabbedContent {...module} key={module._key} />
case 'testimonial-list':
return <TestimonialList {...module} key={module._key} />
case 'testimonial.featured':
Expand Down

0 comments on commit c402a68

Please sign in to comment.