turned template into create cli
This commit is contained in:
243
template/apps/client/src/components/sanity-block.tsx
Normal file
243
template/apps/client/src/components/sanity-block.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { PortableText, createDataAttribute, stegaClean } from "next-sanity";
|
||||
import {
|
||||
getFileAsset,
|
||||
getImageAsset,
|
||||
getImageDimensions,
|
||||
SanityFileAsset,
|
||||
} from "@sanity/asset-utils";
|
||||
import type {
|
||||
BlockContent,
|
||||
Button as SanityButton,
|
||||
SanityImageAsset,
|
||||
} from "@/sanity/sanity.types";
|
||||
import { client } from "@/sanity/client";
|
||||
import LinkButton from "./link-button";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { useDeconstructLink } from "@/lib/link-client";
|
||||
import { dynamicHeight, generateImageUrl } from "@/lib/image-url";
|
||||
import { Button } from "./ui/button";
|
||||
import { sanityConnection } from "@repo/sanity-connection";
|
||||
|
||||
const { projectId, dataset } = client.config();
|
||||
|
||||
export function Callout(props: any) {
|
||||
if (!props) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="bg-secondary-500 text-secondary-50 shadow-lg rounded-md p-6 border my-6 border-gray-200">
|
||||
<PortableText
|
||||
value={props.value.content}
|
||||
components={{
|
||||
block: {
|
||||
h1: ({ children }: any) => <h1>{children}</h1>,
|
||||
h2: ({ children }: any) => <h2>{children}</h2>,
|
||||
h3: ({ children }: any) => <h3>{children}</h3>,
|
||||
h4: ({ children }: any) => <h4>{children}</h4>,
|
||||
h5: ({ children }: any) => <h5>{children}</h5>,
|
||||
h6: ({ children }: any) => <h6>{children}</h6>,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortableButton(value: { value: SanityButton }) {
|
||||
const linkData = useDeconstructLink(value.value.link);
|
||||
return (
|
||||
<div className="flex justify-left">
|
||||
<LinkButton
|
||||
className="px-5"
|
||||
text={value.value.text ?? ""}
|
||||
color={"default"}
|
||||
linkData={linkData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortableImage({
|
||||
value,
|
||||
isInline,
|
||||
}: {
|
||||
value: SanityImageAsset;
|
||||
isInline: boolean;
|
||||
}) {
|
||||
const { width, height } = getImageDimensions(value);
|
||||
const imageAsset = getImageAsset(value, {
|
||||
projectId: sanityConnection.projectId ?? "",
|
||||
dataset: sanityConnection.dataset ?? "",
|
||||
});
|
||||
const attr = createDataAttribute({
|
||||
id: imageAsset._id,
|
||||
type: imageAsset._type,
|
||||
workspace: "production",
|
||||
});
|
||||
|
||||
return (
|
||||
<Image
|
||||
data-sanity={attr("body")}
|
||||
src={generateImageUrl(value)}
|
||||
width={
|
||||
isInline ? (width >= 100 ? 100 : width) : width >= 1200 ? 1200 : width
|
||||
}
|
||||
height={dynamicHeight(height, width, isInline)}
|
||||
alt={value.altText || " "}
|
||||
loading="lazy"
|
||||
className="border rounded-md shadow-md"
|
||||
style={{
|
||||
display: isInline ? "inline-block" : "block",
|
||||
aspectRatio: width / height,
|
||||
maxHeight: "400px",
|
||||
maxWidth: "100%",
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortableFile({ value }: { value: SanityFileAsset }) {
|
||||
const fileAsset = getFileAsset(value, {
|
||||
projectId: projectId ?? "",
|
||||
dataset: dataset ?? "",
|
||||
});
|
||||
const attr = createDataAttribute({
|
||||
id: fileAsset._id,
|
||||
type: fileAsset._type,
|
||||
workspace: "production",
|
||||
});
|
||||
|
||||
const filename: string =
|
||||
fileAsset?.originalFilename ||
|
||||
`${fileAsset.extension.charAt(0).toUpperCase() + fileAsset.extension?.slice(1)} herunterladen`;
|
||||
|
||||
return (
|
||||
<div className="flex justify-left">
|
||||
<LinkButton
|
||||
linkData={{ href: fileAsset.url, target: "" }}
|
||||
data-sanity={attr("body")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
download
|
||||
showIcon={false}
|
||||
extraIcon={DownloadIcon}
|
||||
text={
|
||||
filename.length > 20
|
||||
? `${filename.slice(0, 10)}...${filename.slice(-10)}`
|
||||
: filename
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomLink(props: { children: React.ReactNode; value?: any }) {
|
||||
const { value, children } = props;
|
||||
|
||||
let linkUrl = null;
|
||||
let target = "_self";
|
||||
|
||||
if (value?.href) {
|
||||
const href = value.href;
|
||||
|
||||
if (href.type === "external" && href.url) {
|
||||
linkUrl = href.url;
|
||||
target = href.blank ? "_blank" : "_self";
|
||||
} else if (href.type === "internal" || href._type === "reference") {
|
||||
const resolved = useDeconstructLink(href);
|
||||
if (resolved) {
|
||||
const ButtonComponent = Button as any;
|
||||
return (
|
||||
<ButtonComponent variant="link" className="p-0 h-auto font-normal" asChild>
|
||||
<Link href={resolved}>
|
||||
{children}
|
||||
</Link>
|
||||
</ButtonComponent>
|
||||
);
|
||||
}
|
||||
} else if (typeof href === "string") {
|
||||
linkUrl = href;
|
||||
}
|
||||
}
|
||||
|
||||
if (linkUrl) {
|
||||
const ButtonComponent = Button as any;
|
||||
return (
|
||||
<ButtonComponent variant="link" className="p-0 h-auto font-normal" asChild>
|
||||
<Link
|
||||
href={linkUrl}
|
||||
target={target}
|
||||
rel={target === "_blank" ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</ButtonComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const components = {
|
||||
types: {
|
||||
image: PortableImage,
|
||||
button: PortableButton,
|
||||
callout: Callout,
|
||||
file: PortableFile,
|
||||
},
|
||||
block: {
|
||||
h1: ({ children }: any) => <h1>{children}</h1>,
|
||||
h2: ({ children }: any) => <h2>{children}</h2>,
|
||||
h3: ({ children }: any) => <h3>{children}</h3>,
|
||||
h4: ({ children }: any) => <h4>{children}</h4>,
|
||||
h5: ({ children }: any) => <h5>{children}</h5>,
|
||||
h6: ({ children }: any) => <h6>{children}</h6>,
|
||||
},
|
||||
marks: {
|
||||
left: ({ children }: { children: React.ReactNode }) => (
|
||||
<span style={{ textAlign: "left", width: "100%", display: "block" }}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
center: ({ children }: { children: React.ReactNode }) => (
|
||||
<span style={{ textAlign: "center", width: "100%", display: "block" }}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
right: ({ children }: { children: React.ReactNode }) => (
|
||||
<span style={{ textAlign: "right", width: "100%", display: "block" }}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
textColor: ({
|
||||
children,
|
||||
value = { value: "" },
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
value?: { value: string };
|
||||
}) => <span style={{ color: stegaClean(value.value) }}>{children}</span>,
|
||||
highlightColor: ({
|
||||
children,
|
||||
value = { value: "" },
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
value?: { value: string };
|
||||
}) => (
|
||||
<span style={{ background: stegaClean(value.value) }}>{children}</span>
|
||||
),
|
||||
link: CustomLink,
|
||||
},
|
||||
};
|
||||
|
||||
export default function SanityBlock({ body }: { body: BlockContent }) {
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
return <PortableText value={body} components={components} />;
|
||||
}
|
||||
Reference in New Issue
Block a user