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.
		
		
		
		
		
			
		
			
				
					
					
						
							86 lines
						
					
					
						
							2.5 KiB
						
					
					
				
			
		
		
	
	
							86 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 { 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 };
 | |
| 
 |