Well, i will share my schema for it:
import { alpha, Box } from "@material-ui/core";
import type { Theme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
import clsx from "clsx";
import merge from "deepmerge";
import gh from "hast-util-sanitize/lib/github.json";
import Image from "next/image";
import React from "react";
import type { ReactMarkdownOptions } from "react-markdown";
import ReactMarkdown from "react-markdown";
import ReactPlayer from "react-player/lazy";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import remarkAutolinkHeadings from "remark-autolink-headings";
import remarkSlug from "remark-slug";
import unwrapImages from "remark-unwrap-images";
import { getBlurUrlFromCloudinary } from "presentation/lib/helpers";
import remarkVideo from "presentation/lib/remarkVideo";
import useGlobalStyles from "presentation/styles/common";
import oceanic from "presentation/styles/material-oceanic";
const useStyles = makeStyles(({ breakpoints, palette }: Theme) => ({
codeBlock: {
"&::-webkit-scrollbar": {
// display: "none",
background: alpha(palette.light.main, 0.1),
},
"&::-webkit-scrollbar-track-piece": {
display: "none",
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: alpha(palette.dark.main, 0.5),
},
},
image: {
position: "relative",
margin: "auto",
[breakpoints.down("xs")]: {
// width: 400,
height: 200,
},
[breakpoints.between("sm", "sm")]: {
// width: 700,
height: 500,
},
[breakpoints.up("md")]: {
// width: 800,
height: 600,
},
},
playerWrapper: {
position: "relative",
paddingTop: "56.25%" /* Player ratio: 100 / (1280 / 720) */,
margin: "30px auto",
},
reactPlayer: {
position: "absolute",
top: 0,
left: 0,
},
}));
const CodeBlock: React.FC<{ className?: string }> = ({ className }) => {
const classes = useStyles();
const match: RegExpExecArray | null = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter
showLineNumbers
startingLineNumber={1}
language={match[1]}
style={oceanic}
lineNumberContainerProps={{
style: { color: "#ddd", paddingRight: "1.625em", float: "left" },
}}
wrapLines
className={classes.codeBlock}
/>
) : (
<code className={clsx(className, classes.codeBlock)} />
);
};
interface IImageBlock {
src?: string;
alt?: string;
title?: string;
}
const ImageBlock: React.FC<IImageBlock> = ({ alt, src, title }) => {
const classes = useStyles();
const globalClasses = useGlobalStyles();
if (typeof src === "undefined")
throw new TypeError("Next Image component requires a src attribute.");
return (
<Box className={clsx(classes.image, globalClasses.centeredImage)}>
<Image
alt={alt}
src={src}
quality={80}
layout="fill"
objectFit="contain"
title={title}
placeholder="blur"
blurDataURL={getBlurUrlFromCloudinary(src)}
/>
</Box>
);
};
const VideoBlock: React.FC<{ src?: string }> = ({ src }) => {
const classes = useStyles();
return (
<div className={classes.playerWrapper}>
<ReactPlayer
className={classes.reactPlayer}
url={src}
controls
playing
width="100%"
height="100%"
/>
</div>
);
};
const _mapProps = (props: ReactMarkdownOptions): ReactMarkdownOptions => ({
...props,
remarkPlugins: [
// RemarkMathPlugin,
// RemarkHighlightPlugin,
remarkSlug,
remarkAutolinkHeadings,
unwrapImages,
remarkVideo,
],
rehypePlugins: [
rehypeRaw,
[rehypeSanitize, merge(gh, { attributes: { code: ["className"] } })],
],
components: {
...props.components,
// math: ({ value }) => <BlockMath>{value}</BlockMath>,
// inlineMath: ({ value }) => <InlineMath>{value}</InlineMath>,
code: CodeBlock,
img: ImageBlock,
video: VideoBlock,
},
});
const Content: React.FC<ReactMarkdownOptions> = (props) => (
<ReactMarkdown {..._mapProps(props)} />
);
export default Content;
This is intended for a rich text field.
For dynamic zone:
const DynamicZone: React.FC<IDynamicZone> = ({ component, className }) => {
const classes = useStyles();
const selectComponent = () => {
switch (component.__typename) {
case "ComponentContentRichText":
return <Content>{component.text}</Content>;
case "ComponentContentExperience":
return <Experience {...component} last={false} />;
case "ComponentContentPersonalInformation":
return <PersonalInformation {...component} />;
case "ComponentFieldsSkill":
return <Skill {...component} />;
}
};
return (
<Typography
variant="body1"
component="section"
className={clsx(classes.dynamicZone, className)}
>
{selectComponent()}
{/*
{
{
"content.rich-text": <Content>{component.text}</Content>,
"content.experience": <Experience {...component} />,
"content.personal-information": (
<PersonalInformation {...component} />
),
"fields.skill": <Skill {...component} />,
}[component.__component]
}
*/}
</Typography>
);
};
export default DynamicZone;