You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
blog.krasnikov.pro/components/mdx-components.tsx

87 lines
2.5 KiB

import Image from 'next/image';
import Link from 'next/link';
import Balancer from 'react-wrap-balancer';
import { Callout } from '@/components/callout';
import { CodeBlock } from '@/components/code-block';
import { TableOfContents } from '@/components/table-of-contents';
import { Video } from '@/components/video';
import { cn } from '@/lib/utils';
/**
* Use <Link> for internal links and <a> for external links and anchors
* and open external links in a new tab
*/
function a({ href, children }: React.HTMLProps<HTMLAnchorElement>) {
if (href && href.startsWith('/')) {
return <Link href={href}>{children}</Link>;
}
if (href && href.startsWith('#')) {
return <a href={href}>{children}</a>;
}
return (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
}
/**
* Use div instead of p elements since p elements have restrictions on what
* elements can be nested inside them
*/
function p(props: React.HTMLProps<HTMLParagraphElement>) {
return <div className={cn('my-4', props.className)} {...props} />;
}
/**
* Image component that uses next/image, with optional caption and width/height
* Example usage: \!\[alt text {{ w: 600, h: 300, cap: "caption text" }}](/path/to/image)
*/
function img({ src, alt }: React.HTMLProps<HTMLImageElement>) {
const _alt = (alt?.split('{')[0].trim() ?? alt) || '';
const props = alt?.split('{')[1];
const width = parseInt(props?.match(/w:\s*(\d+)/)?.[1] ?? '700');
const height = parseInt(props?.match(/h:\s*(\d+)/)?.[1] ?? '400');
const caption = props?.match(/cap:\s*"(.*?)"/)?.[1];
return (
<figure
className="mx-auto mb-6 mt-3 flex h-fit w-fit flex-col rounded bg-slate-300/20 dark:bg-rose-50/25"
aria-label={_alt}
>
<Image
src={src || ''}
alt={_alt}
width={width}
height={height}
className={cn('rounded', caption && 'rounded-b-none')}
/>
{caption && (
<figcaption
className={cn(
'm-0 rounded-b-[3px] px-6 py-1 text-center',
'bg-slate-300/50 text-slate-700',
'dark:bg-rose-50/5 dark:text-rose-50',
)}
style={{
maxWidth: width,
}}
>
<Balancer>{caption}</Balancer>
</figcaption>
)}
</figure>
);
}
/**
* Code block component with copy button
*/
function pre({ children }: React.HTMLProps<HTMLPreElement>) {
return <CodeBlock>{children}</CodeBlock>;
}
export const MDXComponents = { a, p, img, pre, TableOfContents, Callout, Video };