Files
supabase/apps/design-system/components/mdx-components.tsx
T
Ivan Vasilov c6cdf4bd53 Migrate off contentlayer2 to Velite (design-system, ui-library, learn) (#48546)
## Summary
- `contentlayer2@0.4.6` is unmaintained and drags in a heavy, stale
dependency graph (esbuild pinned to 0.17–0.20, mdx-bundler, old
`@opentelemetry/core`) that was the recurring source of vuln bumps.
- Migrates all three apps that used it — `design-system`, `ui-library`,
`learn` — to [Velite](https://velite.js.org), preserving the generated
typed `allDocs`/`Doc` collection and the `body.code` + `useMDXComponent`
runtime via a small shared local hook.
- Same MDX pipeline (remark-gfm, remark-code-import, rehype-slug,
rehype-pretty-code w/ Shiki compat + local theme,
rehype-autolink-headings, custom
`__rawString__`/`__src__`/`__event__`/`__style__` visitors) ported 1:1
into each app's `velite.config.js`.
- `learn`'s extra frontmatter fields (`chapterNumber`, `explore`,
`courseHero`) are now backed by real Velite/Zod schema types, so the
`(doc as any)` casts in `get-next-page.ts` / `get-current-chapter.ts` /
the doc page could be dropped.
- `next.config.mjs` no longer wraps with `withContentlayer`; since
Velite has no Next.js webpack-plugin equivalent, each app's `dev` script
now runs `velite dev` and `next dev` in parallel via `npm-run-all`.

Ref:
[FE-3861](https://linear.app/supabase/issue/FE-3861/migrate-off-contentlayer2-learn-ui-library-design-system-to-shed)

## Test plan
- [x] `pnpm build:content` (Velite build) succeeds for all three apps
- [x] `pnpm typecheck` passes for all three apps
- [ ] Manual smoke test of `pnpm dev` for each app in a browser (docs
pages render, TOC, copy-button, code highlighting)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Improved content generation across documentation, learning materials,
and the UI library for more consistent pages.
* Preserved MDX rendering, navigation, table of contents, course
metadata, source previews, and component examples.
* Improved consistency when displaying documentation and interactive
examples.
* Improved application loading by optimizing how interface components
are delivered.
* **Chores**
* Streamlined content compilation and development workflows across the
design system, learning area, and UI library.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-04 15:49:44 +02:00

294 lines
9.4 KiB
TypeScript

'use client'
import Image from 'next/image'
import Link from 'next/link'
import * as React from 'react'
// import { NpmCommands } from 'types/unist'
// import { Event } from '@/lib/events'
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Alert,
AlertDescription,
AlertTitle,
AspectRatio,
cn,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { ComponentProps } from './component-props'
import { SonnerExpandConfig } from './sonner-expand-config'
import { SonnerPositionConfig } from './sonner-expand-position'
// import { FrameworkDocs } from '@/components/framework-docs'
import { StyleWrapper } from './style-wrapper'
import { Callout } from '@/components/callout'
import { CodeBlockWrapper } from '@/components/code-block-wrapper'
import { CodeFragment } from '@/components/code-fragment'
import { ColorPalette } from '@/components/color-palette'
import { Colors } from '@/components/colors'
import { ComponentExample } from '@/components/component-example'
import { ComponentPreview } from '@/components/component-preview'
import { ComponentSource } from '@/components/component-source'
import {
CopyButton,
// CopyNpmCommandButton
} from '@/components/copy-button'
import { Icons } from '@/components/icons'
import { ThemeSettings } from '@/components/theme-settings'
import { useConfig } from '@/hooks/use-config'
import { useMDXComponent } from '@/lib/use-mdx-component'
import { Style } from '@/registry/styles'
const components = {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Alert,
AlertTitle,
AlertDescription,
h1: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1 className={cn('font-heading mt-2 scroll-m-20 text-4xl font-bold', className)} {...props} />
),
h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h2
className={cn(
'font-heading mt-12 scroll-m-20 border-b pb-2 text-2xl tracking-tight first:mt-0',
className
)}
{...props}
/>
),
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h3
className={cn('font-heading mt-8 scroll-m-20 text-xl tracking-tight', className)}
{...props}
/>
),
h4: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h4
className={cn('font-heading mt-8 scroll-m-20 text-lg tracking-tight', className)}
{...props}
/>
),
h5: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h5 className={cn('mt-8 scroll-m-20 text-lg tracking-tight', className)} {...props} />
),
h6: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h6 className={cn('mt-8 scroll-m-20 text-base tracking-tight', className)} {...props} />
),
a: ({ className, ...props }: React.HTMLAttributes<HTMLAnchorElement>) => (
<a
className={cn(
'text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-brand hover:decoration-2',
className
)}
{...props}
/>
),
p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
<p className={cn('leading-7 not-first:mt-6 text-foreground-light', className)} {...props} />
),
ul: ({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) => (
<ul className={cn('my-6 ml-6 list-disc', className)} {...props} />
),
ol: ({ className, ...props }: React.HTMLAttributes<HTMLOListElement>) => (
<ol className={cn('my-6 ml-6 list-decimal', className)} {...props} />
),
li: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
<li className={cn('mt-2', className)} {...props} />
),
blockquote: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
<blockquote className={cn('mt-6 border-l-2 pl-6 italic', className)} {...props} />
),
img: ({ className, alt, ...props }: React.ImgHTMLAttributes<HTMLImageElement>) => (
// eslint-disable-next-line @next/next/no-img-element
<img className={cn('rounded-md', className)} alt={alt} {...props} />
),
hr: ({ ...props }: React.HTMLAttributes<HTMLHRElement>) => (
<hr className="my-4 md:my-8" {...props} />
),
table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => (
<div className="my-6 w-full overflow-y-auto">
<table className={cn('w-full', className)} {...props} />
</div>
),
tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
<tr className={cn('m-0 border-t p-0 even:bg-surface-75/75', className)} {...props} />
),
th: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<th
className={cn(
'border px-4 py-2 text-left font-normal [[align=center]]:text-center [[align=right]]:text-right',
className
)}
{...props}
/>
),
td: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<td
className={cn(
'border text-foreground-light px-4 py-2 text-left [[align=center]]:text-center [[align=right]]:text-right',
className
)}
{...props}
/>
),
pre: (
{
className,
__rawString__,
// __npmCommand__,
// __yarnCommand__,
// __pnpmCommand__,
// __bunCommand__,
__withMeta__,
__src__,
// __event__,
__style__,
...props
}: React.HTMLAttributes<HTMLPreElement> & {
__style__?: Style['name']
__rawString__?: string
__withMeta__?: boolean
__src__?: string
// __event__?: Event['name']
}
// & NpmCommands
) => {
return (
<StyleWrapper styleName={__style__}>
<pre
className={cn(
'mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg border bg-surface-75/75 py-4',
className
)}
{...props}
/>
{__rawString__ && (
// !__npmCommand__ &&
<CopyButton
value={__rawString__}
src={__src__}
// event={__event__}
className={cn('absolute right-4 top-4', __withMeta__ && 'top-16')}
/>
)}
{/* {__npmCommand__ && __yarnCommand__ && __pnpmCommand__ && __bunCommand__ && (
<CopyNpmCommandButton
commands={{
__npmCommand__,
__yarnCommand__,
__pnpmCommand__,
__bunCommand__,
}}
className={cn('absolute right-4 top-4', __withMeta__ && 'top-16')}
/>
)} */}
</StyleWrapper>
)
},
code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
<code
className={cn(
'relative rounded-sm bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm',
className
)}
{...props}
/>
),
Image,
Callout,
ComponentPreview,
ComponentExample,
ComponentSource,
ComponentProps,
AspectRatio,
CodeBlockWrapper: ({ ...props }) => <CodeBlockWrapper className="rounded-md border" {...props} />,
Step: ({ className, ...props }: React.ComponentProps<'h3'>) => (
<h3
className={cn(
'font-heading mt-8 scroll-m-20 text-xl font-semibold tracking-tight',
className
)}
{...props}
/>
),
Steps: ({ ...props }) => (
<div className="[&>h3]:step steps mb-12 ml-4 border-l pl-8 [counter-reset:step]" {...props} />
),
Tabs: ({ className, ...props }: React.ComponentProps<typeof Tabs>) => (
<Tabs className={cn('relative mt-6 w-full', className)} {...props} />
),
TabsList: ({ className, ...props }: React.ComponentProps<typeof TabsList>) => (
<TabsList
className={cn('w-full justify-start rounded-none border-b bg-transparent p-0', className)}
{...props}
/>
),
TabsTrigger: ({ className, ...props }: React.ComponentProps<typeof TabsTrigger>) => (
<TabsTrigger
className={cn(
'relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none',
className
)}
{...props}
/>
),
TabsContent: ({ className, ...props }: React.ComponentProps<typeof TabsContent>) => (
<TabsContent
className={cn(
'relative [&_h3.font-heading]:text-base [&_h3.font-heading]:font-semibold',
className
)}
{...props}
/>
),
// FrameworkDocs: ({ className, ...props }: React.ComponentProps<typeof FrameworkDocs>) => (
// <FrameworkDocs className={cn(className)} {...props} />
// ),
Link: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
<Link className={cn('font-medium underline underline-offset-4', className)} {...props} />
),
LinkedCard: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
<Link
className={cn(
'flex w-full flex-col items-center rounded-xl border bg-card p-6 text-card-foreground shadow-sm transition-colors hover:bg-muted/50 sm:p-10',
className
)}
{...props}
/>
),
Colors,
ColorPalette,
Icons,
ThemeSettings,
CodeFragment,
Admonition,
SonnerExpandConfig,
SonnerPositionConfig,
}
interface MdxProps {
code: string
}
export function Mdx({ code }: MdxProps) {
const [config] = useConfig()
const Component = useMDXComponent(code, {
style: config.style,
})
return (
<div className="mdx">
<Component components={components} />
</div>
)
}