feat: implement accordion content type

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe
2024-05-24 13:19:14 +07:00
parent a1d65a54c1
commit e0da620ac9
13 changed files with 227 additions and 123 deletions

View File

@@ -0,0 +1,23 @@
'use client';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
import { ReactNode } from 'react';
const AccordionBlockItem = ({ title, children }: { title: string; children: ReactNode }) => {
return (
<Disclosure as="div" className="pt-6">
<dt>
<DisclosureButton className="group flex w-full items-start justify-between text-left text-gray-900">
<span className="text-lg font-semibold leading-7">{title}</span>
<ChevronDownIcon className="size-5 group-data-[open]:rotate-180" />
</DisclosureButton>
</dt>
<DisclosurePanel as="dd" className="mt-2 flex flex-col gap-4 py-4 text-base text-gray-800">
{children}
</DisclosurePanel>
</Disclosure>
);
};
export default AccordionBlockItem;

View File

@@ -0,0 +1,39 @@
import { getMetaobjectById, getMetaobjectsByIds } from 'lib/shopify';
import { Metaobject } from 'lib/shopify/types';
import AccordionBlockItem from './accordion-block-item';
import PageContent from './page-content';
const AccordionItem = async ({ id }: { id: string }) => {
const accordionObject = await getMetaobjectById(id);
if (!accordionObject) return null;
const content = await getMetaobjectsByIds(JSON.parse(accordionObject.accordion_content || '[]'));
return (
<AccordionBlockItem title={accordionObject.title || 'Section Title'}>
{content.map((block) => (
<PageContent block={block} key={block.id} />
))}
</AccordionBlockItem>
);
};
const AccordionBlock = async ({ block }: { block: Metaobject }) => {
const accordionItemIds = JSON.parse(block.accordion || '[]') as string[];
return (
<div className="divide-y divide-gray-900/10">
{block.title && (
<h3 className="mb-7 text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
)}
<dl className="w-full space-y-6 divide-y divide-gray-900/10">
{accordionItemIds.map((id) => (
<AccordionItem key={id} id={id} />
))}
</dl>
</div>
);
};
export default AccordionBlock;

View File

@@ -1,7 +1,7 @@
import Grid from 'components/grid';
import DynamicHeroIcon from 'components/hero-icon';
import { getMetaobjects, getMetaobjectsByIds } from 'lib/shopify';
import { PageContent, ScreenSize } from 'lib/shopify/types';
import { Metaobject, ScreenSize } from 'lib/shopify/types';
export const IconBlockPlaceholder = () => {
return (
@@ -13,15 +13,10 @@ export const IconBlockPlaceholder = () => {
);
};
const IconWithTextBlock = async ({ content }: { content: PageContent }) => {
// for icon with text content, we only need the first metaobject as the array always contains only one element due to the metafield definition set up on Shopify
const metaobject = content.metaobjects[0];
if (!metaobject) return null;
const IconWithTextBlock = async ({ block }: { block: Metaobject }) => {
const [contentBlocks, layouts, screenSizes] = await Promise.all([
getMetaobjectsByIds(metaobject.content ? JSON.parse(metaobject.content) : []),
getMetaobjectsByIds(metaobject.layouts ? JSON.parse(metaobject.layouts) : []),
getMetaobjectsByIds(block.content ? JSON.parse(block.content) : []),
getMetaobjectsByIds(block.layouts ? JSON.parse(block.layouts) : []),
getMetaobjects('screen_sizes')
]);
@@ -75,15 +70,18 @@ const IconWithTextBlock = async ({ content }: { content: PageContent }) => {
return (
<div className="flex flex-col gap-5 px-4 md:px-0">
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
{block.title ? (
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
) : null}
<Grid className={validClassnames}>
{contentBlocks.map((block) => (
<Grid.Item key={block.id} className="flex flex-col gap-2">
{block.icon_name && (
<DynamicHeroIcon icon={block.icon_name} className="w-16 text-secondary" />
)}
<div className="text-lg font-medium">{block.title}</div>
<p className="text-base text-gray-800">{block.content}</p>
{block.title && <div className="text-lg font-medium">{block.title}</div>}
{block.content && <p className="text-base text-gray-800">{block.content}</p>}
</Grid.Item>
))}
</Grid>

View File

@@ -1,35 +1,34 @@
import { PageContent } from 'lib/shopify/types';
import { Metaobject } from 'lib/shopify/types';
import { Suspense } from 'react';
import ImageDisplay from './image-display';
import RichTextDisplay from './rich-text-display';
const ImageWithTextBlock = ({ content }: { content: PageContent }) => {
if (!content.metaobjects.length) return null;
const ImageWithTextBlock = ({ block }: { block: Metaobject }) => {
const description = block.description ? JSON.parse(block.description) : null;
return (
<div className="flex flex-col gap-10">
{content.metaobjects.map((metaobject) => {
const contentBlocks = JSON.parse(metaobject.description || '{}');
return (
<div className="flex flex-col gap-6 px-4 md:px-0" key={metaobject.id}>
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
<div className="relative col-span-1">
<Suspense>
<ImageDisplay
title={metaobject.title as string}
fileId={metaobject.file as string}
/>
</Suspense>
</div>
<div className="col-span-2">
<RichTextDisplay contentBlocks={contentBlocks.children} />
</div>
</div>
<div className="flex flex-col gap-6 px-4 md:px-0">
{block.title && (
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
)}
{description ? (
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
<div className="relative col-span-1">
<Suspense>
<ImageDisplay title={block.title || 'Image Preview'} fileId={block.file as string} />
</Suspense>
</div>
);
})}
<div className="col-span-2">
<RichTextDisplay contentBlocks={description.children} />
</div>
</div>
) : (
<div className="relative w-full">
<Suspense>
<ImageDisplay title={block.title || 'Image Preview'} fileId={block.file as string} />
</Suspense>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,24 @@
import { Metaobject, PageType } from 'lib/shopify/types';
import { Suspense } from 'react';
import AccordionBlock from './accordion-block';
import IconWithTextBlock, { IconBlockPlaceholder } from './icon-with-text-block';
import ImageWithTextBlock from './image-with-text-block';
import TextBlock from './text-block';
const PageContent = ({ block }: { block: Metaobject }) => {
// eslint-disable-next-line no-unused-vars
const contentMap: Record<PageType, (block: Metaobject) => JSX.Element> = {
icon_content_section: (block) => (
<Suspense fallback={<IconBlockPlaceholder />}>
<IconWithTextBlock block={block} />
</Suspense>
),
image: (block) => <ImageWithTextBlock block={block} />,
page_section: (block) => <TextBlock block={block} />,
accordion: (block) => <AccordionBlock block={block} />
};
return contentMap[block.type as PageType](block);
};
export default PageContent;

View File

@@ -1,10 +1,18 @@
type Text = {
type: 'text';
value: string;
bold?: boolean;
};
type Content =
| { type: 'paragraph'; children: Array<{ type: 'text'; value: string; bold?: boolean }> }
| { type: 'paragraph'; children: Text[] }
| Text
| {
type: 'text';
value: string;
bold?: boolean;
};
type: 'list';
listType: 'bullet' | 'ordered';
children: Array<{ type: 'listItem'; children: Text[] }>;
}
| { type: 'listItem'; children: Text[] };
const RichTextBlock = ({ block }: { block: Content }) => {
if (block.type === 'text') {
@@ -15,6 +23,22 @@ const RichTextBlock = ({ block }: { block: Content }) => {
);
}
if (block.type === 'listItem') {
return block.children.map((child, index) => <RichTextBlock key={index} block={child} />);
}
if (block.type === 'list' && block.listType === 'ordered') {
return (
<ol className="ml-10 list-decimal">
{block.children.map((child, index) => (
<li key={index}>
<RichTextBlock block={child} />
</li>
))}
</ol>
);
}
return (
<p className="text-gray-800">
{block.children.map((child, index) => (

View File

@@ -1,21 +1,18 @@
import { PageContent } from 'lib/shopify/types';
import { Metaobject } from 'lib/shopify/types';
import RichTextDisplay from './rich-text-display';
const TextBlock = ({ content }: { content: PageContent }) => {
if (!content.metaobjects.length) return null;
const TextBlock = ({ block }: { block: Metaobject }) => {
const content = JSON.parse(block.content || '{}');
return (
<div className="flex flex-col gap-8">
{content.metaobjects.map((metaobject) => {
const contentBlocks = JSON.parse(metaobject.content || '{}');
<div className="flex flex-col gap-5 px-4 md:px-0">
{block.title && (
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
)}
return (
<div className="flex flex-col gap-5 px-4 md:px-0" key={metaobject.id}>
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
<RichTextDisplay contentBlocks={contentBlocks.children} />
</div>
);
})}
<RichTextDisplay contentBlocks={content.children} />
</div>
</div>
);
};