mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
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 -->
This commit is contained in:
1 parent
1308a1d0d0
commit
c6cdf4bd53
45 files changed
+971
-2194
No files matched your search
@@ -39,3 +39,4 @@ next-env.d.ts
|
||||
|
||||
# Generated by `pnpm generate:registry` (`scripts/build-registry.mts`)
|
||||
/__registry__
|
||||
.velite
|
||||
@@ -9,7 +9,6 @@ import { absoluteUrl } from '@/lib/utils'
|
||||
import '@/styles/code-block-variables.css'
|
||||
import '@/styles/mdx.css'
|
||||
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
@@ -17,6 +16,8 @@ import { notFound } from 'next/navigation'
|
||||
import Balancer from 'react-wrap-balancer'
|
||||
import { ScrollArea, Separator } from 'ui'
|
||||
|
||||
import { allDocs } from '@/.velite'
|
||||
|
||||
interface DocPageProps {
|
||||
params: Promise<{
|
||||
slug: string[]
|
||||
@@ -83,7 +84,7 @@ export default async function DocPage(props: DocPageProps) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const toc = await getTableOfContents(doc.body.raw)
|
||||
const toc = await getTableOfContents(doc.raw)
|
||||
const breadcrumbSegments = getBreadcrumbSegments(doc)
|
||||
|
||||
return (
|
||||
@@ -128,7 +129,7 @@ export default async function DocPage(props: DocPageProps) {
|
||||
<Separator className="mb-6" />
|
||||
<SourcePanel doc={doc} />
|
||||
<div className="pb-12">
|
||||
<Mdx code={doc.body.code} />
|
||||
<Mdx code={doc.code} />
|
||||
</div>
|
||||
<DocsPager doc={doc} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useMDXComponent } from 'next-contentlayer2/hooks'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import * as React from 'react'
|
||||
@@ -43,6 +42,7 @@ import {
|
||||
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 = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Doc } from 'contentlayer/generated'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { Doc } from '@/.velite'
|
||||
import { docsConfig } from '@/config/docs'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { NavItem, NavItemWithChildren } from '@/types/nav'
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from 'next/link'
|
||||
import { forwardRef } from 'react'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { Doc } from '@/.contentlayer/generated'
|
||||
import { Doc } from '@/.velite'
|
||||
|
||||
const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> & { doc: Doc }>(
|
||||
({ doc, children, ...props }, ref) => {
|
||||
|
||||
@@ -4,8 +4,6 @@ description: Displays a card with header, content, and footer.
|
||||
component: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="card-with-form" />
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
@@ -65,7 +63,3 @@ import {
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
<ComponentPreview name="card-demo" peekCode wide />
|
||||
@@ -7,8 +7,6 @@ links:
|
||||
api: https://www.embla-carousel.com/api
|
||||
---
|
||||
|
||||
<ComponentPreview name="carousel-demo" peekCode wide />
|
||||
|
||||
## About
|
||||
|
||||
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
|
||||
@@ -42,7 +40,7 @@ npm install embla-carousel-react
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="carousel" />
|
||||
{/* <ComponentSource name="carousel" /> */}
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
@@ -82,8 +80,6 @@ import {
|
||||
|
||||
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
|
||||
|
||||
<ComponentPreview name="carousel-size" />
|
||||
|
||||
```tsx title="Example" showLineNumbers {4-6}
|
||||
// 33% of the carousel width.
|
||||
<Carousel>
|
||||
@@ -120,8 +116,6 @@ You can always adjust this in your own project if you need to.
|
||||
|
||||
</Callout>
|
||||
|
||||
<ComponentPreview name="carousel-spacing" />
|
||||
|
||||
```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/
|
||||
<Carousel>
|
||||
<CarouselContent className="-ml-4">
|
||||
@@ -146,8 +140,6 @@ You can always adjust this in your own project if you need to.
|
||||
|
||||
Use the `orientation` prop to set the orientation of the carousel.
|
||||
|
||||
<ComponentPreview name="carousel-orientation" />
|
||||
|
||||
```tsx showLineNumbers /vertical | horizontal/
|
||||
<Carousel orientation="vertical | horizontal">
|
||||
<CarouselContent>
|
||||
@@ -181,8 +173,6 @@ You can pass options to the carousel using the `opts` prop. See the [Embla Carou
|
||||
|
||||
Use a state and the `setApi` props to get an instance of the carousel API.
|
||||
|
||||
<ComponentPreview name="carousel-api" />
|
||||
|
||||
```tsx showLineNumbers {1,4,22}
|
||||
import { type CarouselApi } from '@/components/ui/carousel'
|
||||
|
||||
@@ -272,6 +262,4 @@ export function Example() {
|
||||
}
|
||||
```
|
||||
|
||||
<ComponentPreview name="carousel-plugin" />
|
||||
|
||||
See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins.
|
||||
@@ -6,8 +6,6 @@ source:
|
||||
shadcn: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="pagination-demo" peekCode wide />
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
@@ -30,7 +28,7 @@ npx shadcn-ui@latest add pagination
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="pagination" />
|
||||
{/* <ComponentSource name="pagination" /> */}
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
|
||||
@@ -4,19 +4,6 @@ description: A composable, themeable and customizable sidebar component.
|
||||
component: true
|
||||
---
|
||||
|
||||
<figure className="flex flex-col gap-4">
|
||||
<ComponentPreview
|
||||
name="sidebar-07"
|
||||
title="Sidebar"
|
||||
type="block"
|
||||
description="A composable, themeable and customizable sidebar component built using shadcn/ui"
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar that collapses to icons.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Sidebars are one of the most complex components to build. They are central
|
||||
to any application and often contain a lot of moving parts.
|
||||
|
||||
@@ -331,17 +318,6 @@ export function AppSidebar() {
|
||||
|
||||
<Step>You've created your first sidebar.</Step>
|
||||
|
||||
<figure className="flex flex-col gap-4">
|
||||
<ComponentPreview
|
||||
name="sidebar-demo"
|
||||
title="Sidebar"
|
||||
type="block"
|
||||
description="Your first sidebar."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">Your first sidebar.</figcaption>
|
||||
</figure>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Components
|
||||
@@ -546,19 +522,6 @@ Use the `SidebarHeader` component to add a sticky header to the sidebar.
|
||||
|
||||
The following example adds a `<DropdownMenu>` to the `SidebarHeader`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-header"
|
||||
title="Sidebar"
|
||||
type="block"
|
||||
description="A sidebar header with a dropdown menu."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar header with a dropdown menu.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers title="components/app-sidebar.tsx"
|
||||
<Sidebar>
|
||||
<SidebarHeader>
|
||||
@@ -592,19 +555,6 @@ Use the `SidebarFooter` component to add a sticky footer to the sidebar.
|
||||
|
||||
The following example adds a `<DropdownMenu>` to the `SidebarFooter`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-footer"
|
||||
title="Sidebar"
|
||||
type="block"
|
||||
description="A sidebar footer with a dropdown menu."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar footer with a dropdown menu.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers title="components/app-sidebar.tsx"
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
@@ -668,17 +618,6 @@ Use the `SidebarGroup` component to create a section within the sidebar.
|
||||
|
||||
A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-group"
|
||||
title="Sidebar Group"
|
||||
type="block"
|
||||
description="A sidebar group."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">A sidebar group.</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
import { Sidebar, SidebarContent, SidebarGroup } from '@/components/ui/sidebar'
|
||||
|
||||
@@ -703,19 +642,6 @@ export function AppSidebar() {
|
||||
|
||||
To make a `SidebarGroup` collapsible, wrap it in a `Collapsible`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-group-collapsible"
|
||||
title="Sidebar Group"
|
||||
type="block"
|
||||
description="A collapsible sidebar group."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A collapsible sidebar group.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
@@ -758,19 +684,6 @@ export function AppSidebar() {
|
||||
}
|
||||
```
|
||||
|
||||
<figure className="flex flex-col gap-4">
|
||||
<ComponentPreview
|
||||
name="sidebar-group-action"
|
||||
title="Sidebar Group"
|
||||
type="block"
|
||||
description="A sidebar group with an action button."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar group with an action button.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## SidebarMenu
|
||||
|
||||
The `SidebarMenu` component is used for building a menu within a `SidebarGroup`.
|
||||
@@ -794,19 +707,6 @@ A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`,
|
||||
|
||||
Here's an example of a `SidebarMenu` component rendering a list of projects.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-menu"
|
||||
title="Sidebar Menu"
|
||||
type="block"
|
||||
description="A sidebar menu with a list of projects."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar menu with a list of projects.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Sidebar>
|
||||
<SidebarContent>
|
||||
@@ -892,19 +792,6 @@ This button works independently of the `SidebarMenuButton` i.e you can have the
|
||||
|
||||
Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-menu-action"
|
||||
title="Sidebar Menu Action"
|
||||
type="block"
|
||||
description="A sidebar menu action with a dropdown menu."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar menu action with a dropdown menu.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
@@ -937,19 +824,6 @@ The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu
|
||||
|
||||
Use `<SidebarMenuSubItem />` and `<SidebarMenuSubButton />` to render a submenu item.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-menu-sub"
|
||||
title="Sidebar Menu Sub"
|
||||
type="block"
|
||||
description="A sidebar menu sub."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar menu with a submenu.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton />
|
||||
@@ -968,17 +842,6 @@ Use `<SidebarMenuSubItem />` and `<SidebarMenuSubButton />` to render a submenu
|
||||
|
||||
To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-menu-collapsible"
|
||||
title="Sidebar Menu"
|
||||
type="block"
|
||||
description="A collapsible sidebar menu."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">A collapsible sidebar menu.</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<SidebarMenu>
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
@@ -1000,19 +863,6 @@ To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub`
|
||||
|
||||
The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-menu-badge"
|
||||
title="Sidebar Menu Badge"
|
||||
type="block"
|
||||
description="A sidebar menu badge."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar menu with a badge.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton />
|
||||
@@ -1104,19 +954,6 @@ The `SidebarRail` component is used to render a rail within a `Sidebar`. This ra
|
||||
|
||||
Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-rsc"
|
||||
title="Sidebar Menu RSC"
|
||||
type="block"
|
||||
description="A sidebar menu using React Server Components."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">
|
||||
A sidebar menu using React Server Components.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers {6} title="Skeleton to show loading state."
|
||||
function NavProjectsSkeleton() {
|
||||
return (
|
||||
@@ -1253,17 +1090,6 @@ function NavProjects() {
|
||||
|
||||
Use the `open` and `onOpenChange` props to control the sidebar.
|
||||
|
||||
<figure className="flex flex-col gap-4 mt-6">
|
||||
<ComponentPreview
|
||||
name="sidebar-controlled"
|
||||
title="Sidebar Controlled"
|
||||
type="block"
|
||||
description="A controlled sidebar."
|
||||
className="w-full"
|
||||
/>
|
||||
<figcaption className="text-center text-sm text-gray-500">A controlled sidebar.</figcaption>
|
||||
</figure>
|
||||
|
||||
```tsx showLineNumbers
|
||||
export function AppSidebar() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
@@ -8,9 +8,9 @@ component: true
|
||||
|
||||
## Examples
|
||||
|
||||
{/* ### Multiple sections */}
|
||||
### Multiple sections
|
||||
|
||||
{/* <ComponentPreview name="inner-side-menu-multiple-sections" peekCode /> */}
|
||||
<ComponentPreview name="inner-side-menu-multiple-sections" peekCode />
|
||||
|
||||
### Collapsible menus
|
||||
|
||||
|
||||
@@ -5,10 +5,29 @@ import React from 'react'
|
||||
import { u } from 'unist-builder'
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
import { Index } from '../__registry__'
|
||||
import { registry } from '../registry/registry'
|
||||
import { styles } from '../registry/styles'
|
||||
import { UnistNode, UnistTree } from '@/types/unist'
|
||||
|
||||
// Resolves registry files from the plain registry data (name/type/files
|
||||
// metadata only) rather than `__registry__` — the generated index that also
|
||||
// wires up `React.lazy` component imports. This plugin only ever needs file
|
||||
// paths to read source off disk, and pulling in the generated index (with its
|
||||
// deep chain of real component + npm dependencies) breaks Velite's esbuild-based
|
||||
// config bundling under pnpm's strict module resolution.
|
||||
function getRegistryFiles(styleName: string, name: string) {
|
||||
const item = registry.find((entry) => entry.name === name)
|
||||
if (!item || item.files.length === 0) {
|
||||
throw new Error(`Registry item "${name}" has no source files`)
|
||||
}
|
||||
// `components:ui` items are the real shadcn primitives shared via `packages/ui`,
|
||||
// not local copies under `registry/${styleName}/` like examples/fragments/blocks.
|
||||
if (item.type === 'components:ui') {
|
||||
return item.files.map((file) => `../../packages/ui/src/components/shadcn/ui/${file}`)
|
||||
}
|
||||
return item.files.map((file) => `registry/${styleName}/${file}`)
|
||||
}
|
||||
|
||||
export function rehypeComponent() {
|
||||
return async (tree: UnistTree) => {
|
||||
visit(tree, (node: UnistNode) => {
|
||||
@@ -39,16 +58,16 @@ export function rehypeComponent() {
|
||||
if (srcPath) {
|
||||
src = srcPath
|
||||
} else {
|
||||
const component = Index[style.name][name]
|
||||
const files = getRegistryFiles(style.name, name)
|
||||
// console.log('got to ELSE STATEMENT')
|
||||
// console.log('filename', fileName)
|
||||
// console.log('name', name)
|
||||
|
||||
src = fileName
|
||||
? component.files.find((file: string) => {
|
||||
? files.find((file: string) => {
|
||||
return file.endsWith(`${fileName}.tsx`) || file.endsWith(`${fileName}.ts`)
|
||||
}) || component.files[0]
|
||||
: component.files[0]
|
||||
}) || files[0]
|
||||
: files[0]
|
||||
// console.log('got to END of ELSE STATEMENT')
|
||||
}
|
||||
|
||||
@@ -108,9 +127,9 @@ export function rehypeComponent() {
|
||||
|
||||
try {
|
||||
for (const style of styles) {
|
||||
const component = Index[style.name][name]
|
||||
const files = getRegistryFiles(style.name, name)
|
||||
// console.log('GOT HERE')
|
||||
const src = component.files[0]
|
||||
const src = files[0]
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
@@ -160,9 +179,9 @@ export function rehypeComponent() {
|
||||
|
||||
try {
|
||||
for (const style of styles) {
|
||||
const component = Index[style.name][name]
|
||||
const files = getRegistryFiles(style.name, name)
|
||||
// console.log('GOT HERE')
|
||||
const src = component.files[0]
|
||||
const src = files[0]
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import * as runtime from 'react/jsx-runtime'
|
||||
|
||||
// Velite compiles MDX to a "function-body" (see velite.config.js `code: s.mdx()`)
|
||||
// that reads its jsx runtime off `arguments[0]` — mirrors the pattern from
|
||||
// https://velite.js.org/guide/using-mdx.
|
||||
const EMPTY_GLOBALS: Record<string, unknown> = {}
|
||||
|
||||
function getMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
const fn = new Function(code)
|
||||
return fn({ ...runtime, ...globals }).default
|
||||
}
|
||||
|
||||
export function useMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
return React.useMemo(() => getMDXComponent(code, globals), [code, globals])
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { withContentlayer } from 'next-contentlayer2'
|
||||
|
||||
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || '/design-system'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
@@ -37,8 +35,6 @@ const nextConfig = {
|
||||
]
|
||||
},
|
||||
// Turbopack configuration to handle .md files with raw-loader
|
||||
// This mirrors the webpack configuration added by withContentlayer
|
||||
// and ensures both bundlers can process content files properly
|
||||
turbopack: {
|
||||
rules: {
|
||||
'*.md': {
|
||||
@@ -49,4 +45,4 @@ const nextConfig = {
|
||||
},
|
||||
}
|
||||
|
||||
export default withContentlayer(nextConfig)
|
||||
export default nextConfig
|
||||
@@ -5,23 +5,22 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "pnpm run generate:registry && run-p --race dev:*",
|
||||
"dev:content": "velite dev",
|
||||
"dev:next": "next dev --turbopack --port 3003",
|
||||
"dev:content": "contentlayer2 dev",
|
||||
"build": "pnpm run build:registry && pnpm run content:build && next build --turbopack",
|
||||
"generate:registry": "tsx ./scripts/build-registry.mts",
|
||||
"build:registry": "pnpm run generate:registry && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
|
||||
"dev": "run-p build:registry build:content && run-p --race dev:*",
|
||||
"build:content": "velite build --strict",
|
||||
"build:registry": "tsx ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
|
||||
"build:next": "next build --turbopack",
|
||||
"build": "run-p build:registry build:content && pnpm build:next",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo __registry__",
|
||||
"typecheck": "pnpm run generate:registry && contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo .contentlayer .velite __registry__",
|
||||
"typecheck": "run-p build:registry build:content && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@tanstack/react-table": "catalog:",
|
||||
"common": "workspace:*",
|
||||
"contentlayer2": "0.4.6",
|
||||
"date-fns": "^2.30.0",
|
||||
"dayjs": "1.11.13",
|
||||
"eslint-config-supabase": "workspace:*",
|
||||
@@ -30,7 +29,6 @@
|
||||
"lucide-react": "*",
|
||||
"markdown-wasm": "^1.2.0",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-data-grid": "7.0.0-beta.47",
|
||||
@@ -51,6 +49,7 @@
|
||||
"ui": "workspace:*",
|
||||
"ui-patterns": "workspace:*",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"velite": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,5 +3,6 @@ import { charts } from '@/registry/charts'
|
||||
import { copyWriting } from '@/registry/copy-writing'
|
||||
import { fragments } from '@/registry/fragments'
|
||||
import { Registry } from '@/registry/schema'
|
||||
import { ui } from '@/registry/ui'
|
||||
|
||||
export const registry: Registry = [...fragments, ...examples, ...charts, ...copyWriting]
|
||||
export const registry: Registry = [...fragments, ...examples, ...charts, ...copyWriting, ...ui]
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Registry } from './schema'
|
||||
|
||||
// Base shadcn/ui primitives. Unlike `examples`/`fragments`/`charts`, these don't
|
||||
// live under `registry/default/*` — their source is the real, shared components
|
||||
// consumed via `import { X } from 'ui'`, at `packages/ui/src/components/shadcn/ui`.
|
||||
// `getRegistryFiles` in `lib/rehype-component.ts` resolves `components:ui` files
|
||||
// against that package path instead of the usual `registry/${style}/` prefix.
|
||||
export const ui: Registry = [
|
||||
{ name: 'accordion', type: 'components:ui', files: ['accordion.tsx'] },
|
||||
{ name: 'alert', type: 'components:ui', files: ['alert.tsx'] },
|
||||
{ name: 'alert-dialog', type: 'components:ui', files: ['alert-dialog.tsx'] },
|
||||
{ name: 'aspect-ratio', type: 'components:ui', files: ['aspect-ratio.tsx'] },
|
||||
{ name: 'avatar', type: 'components:ui', files: ['avatar.tsx'] },
|
||||
{ name: 'badge', type: 'components:ui', files: ['badge.tsx'] },
|
||||
{ name: 'breadcrumb', type: 'components:ui', files: ['breadcrumb.tsx'] },
|
||||
{ name: 'button', type: 'components:ui', files: ['button.tsx'] },
|
||||
{ name: 'button-group', type: 'components:ui', files: ['button-group.tsx'] },
|
||||
{ name: 'calendar', type: 'components:ui', files: ['calendar.tsx'] },
|
||||
{ name: 'card', type: 'components:ui', files: ['card.tsx'] },
|
||||
{ name: 'chart', type: 'components:ui', files: ['chart.tsx'] },
|
||||
{ name: 'checkbox', type: 'components:ui', files: ['checkbox.tsx'] },
|
||||
{ name: 'collapsible', type: 'components:ui', files: ['collapsible.tsx'] },
|
||||
{ name: 'command', type: 'components:ui', files: ['command.tsx'] },
|
||||
{ name: 'context-menu', type: 'components:ui', files: ['context-menu.tsx'] },
|
||||
{ name: 'dialog', type: 'components:ui', files: ['dialog.tsx'] },
|
||||
{ name: 'drawer', type: 'components:ui', files: ['drawer.tsx'] },
|
||||
{ name: 'dropdown-menu', type: 'components:ui', files: ['dropdown-menu.tsx'] },
|
||||
{ name: 'field', type: 'components:ui', files: ['field.tsx'] },
|
||||
{ name: 'form', type: 'components:ui', files: ['form.tsx'] },
|
||||
{ name: 'hover-card', type: 'components:ui', files: ['hover-card.tsx'] },
|
||||
{ name: 'input', type: 'components:ui', files: ['input.tsx'] },
|
||||
{ name: 'input-group', type: 'components:ui', files: ['input-group.tsx'] },
|
||||
{ name: 'input-otp', type: 'components:ui', files: ['input-otp.tsx'] },
|
||||
{ name: 'label', type: 'components:ui', files: ['label.tsx'] },
|
||||
{ name: 'menubar', type: 'components:ui', files: ['menubar.tsx'] },
|
||||
{ name: 'navigation-menu', type: 'components:ui', files: ['navigation-menu.tsx'] },
|
||||
{ name: 'popover', type: 'components:ui', files: ['popover.tsx'] },
|
||||
{ name: 'progress', type: 'components:ui', files: ['progress.tsx'] },
|
||||
{ name: 'radio-group', type: 'components:ui', files: ['radio-group.tsx'] },
|
||||
{ name: 'resizable', type: 'components:ui', files: ['resizable.tsx'] },
|
||||
{ name: 'scroll-area', type: 'components:ui', files: ['scroll-area.tsx'] },
|
||||
{ name: 'select', type: 'components:ui', files: ['select.tsx'] },
|
||||
{ name: 'separator', type: 'components:ui', files: ['separator.tsx'] },
|
||||
{ name: 'sheet', type: 'components:ui', files: ['sheet.tsx'] },
|
||||
{ name: 'sidebar', type: 'components:ui', files: ['sidebar.tsx'] },
|
||||
{ name: 'skeleton', type: 'components:ui', files: ['skeleton.tsx'] },
|
||||
{ name: 'slider', type: 'components:ui', files: ['slider.tsx'] },
|
||||
{ name: 'sonner', type: 'components:ui', files: ['sonner.tsx'] },
|
||||
{ name: 'sonner-progress', type: 'components:ui', files: ['sonner-progress.tsx'] },
|
||||
{ name: 'switch', type: 'components:ui', files: ['switch.tsx'] },
|
||||
{ name: 'table', type: 'components:ui', files: ['table.tsx'] },
|
||||
{ name: 'tabs', type: 'components:ui', files: ['tabs.tsx'] },
|
||||
{ name: 'text-area', type: 'components:ui', files: ['text-area.tsx'] },
|
||||
{ name: 'textarea', type: 'components:ui', files: ['textarea.tsx'] },
|
||||
{ name: 'toggle', type: 'components:ui', files: ['toggle.tsx'] },
|
||||
{ name: 'toggle-group', type: 'components:ui', files: ['toggle-group.tsx'] },
|
||||
{ name: 'tooltip', type: 'components:ui', files: ['tooltip.tsx'] },
|
||||
]
|
||||
@@ -6,17 +6,10 @@
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"contentlayer/generated": ["./.contentlayer/generated"],
|
||||
"icons/*": ["./../../packages/icons/*"]
|
||||
},
|
||||
"plugins": [{ "name": "next" }]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".contentlayer/generated"
|
||||
],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".velite"],
|
||||
"exclude": ["node_modules", "./scripts/build-registry.mts"]
|
||||
}
|
||||
@@ -4,19 +4,8 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"env": [
|
||||
"NEXT_PUBLIC_BASE_PATH",
|
||||
"NEXT_PUBLIC_APP_URL",
|
||||
// These envs are used in the packages
|
||||
"NEXT_PUBLIC_STORAGE_KEY",
|
||||
"NEXT_PUBLIC_AUTH_DEBUG_KEY",
|
||||
"NEXT_PUBLIC_AUTH_PERSISTED_KEY",
|
||||
"NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY",
|
||||
"NEXT_PUBLIC_AUTH_DETECT_SESSION_IN_URL",
|
||||
"NEXT_PUBLIC_GOTRUE_URL",
|
||||
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
|
||||
],
|
||||
"outputs": [".next/**", "!.next/cache/**", "!.next/dev/**/*", ".contentlayer/**"],
|
||||
"env": ["NEXT_PUBLIC_BASE_PATH", "NEXT_PUBLIC_APP_URL"],
|
||||
"outputs": [".next/**", "!.next/cache/**", "!.next/dev/**/*", ".velite/**"],
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,169 +1,74 @@
|
||||
import path from 'path'
|
||||
import { getHighlighter, loadTheme } from '@shikijs/compat'
|
||||
import { defineDocumentType, defineNestedType, makeSource } from 'contentlayer2/source-files'
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
||||
import rehypePrettyCode from 'rehype-pretty-code'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import { codeImport } from 'remark-code-import'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { defineConfig, s } from 'velite'
|
||||
|
||||
/** @type {import('contentlayer2/source-files').ComputedFields} */
|
||||
const computedFields = {
|
||||
slug: {
|
||||
type: 'string',
|
||||
resolve: (doc) => `/${doc._raw.flattenedPath}`,
|
||||
},
|
||||
slugAsParams: {
|
||||
type: 'string',
|
||||
resolve: (doc) => doc._raw.flattenedPath,
|
||||
},
|
||||
}
|
||||
import { rehypeComponent } from './lib/rehype-component'
|
||||
|
||||
const LinksProperties = defineNestedType(() => ({
|
||||
name: 'LinksProperties',
|
||||
fields: {
|
||||
doc: {
|
||||
type: 'string',
|
||||
},
|
||||
api: {
|
||||
type: 'string',
|
||||
const LinksProperties = s.object({
|
||||
doc: s.string().optional(),
|
||||
api: s.string().optional(),
|
||||
})
|
||||
|
||||
const NestedProperties = s.object({
|
||||
radix: s.boolean().optional(),
|
||||
shadcn: s.boolean().optional(),
|
||||
vaul: s.boolean().optional(),
|
||||
inputOtp: s.boolean().optional(),
|
||||
reactAccessibleTreeview: s.boolean().optional(),
|
||||
recharts: s.boolean().optional(),
|
||||
})
|
||||
|
||||
const docs = s
|
||||
.object({
|
||||
title: s.string(),
|
||||
description: s.string(),
|
||||
published: s.boolean().default(true),
|
||||
links: LinksProperties.optional(),
|
||||
featured: s.boolean().default(false),
|
||||
component: s.boolean().default(false),
|
||||
fragment: s.boolean().default(false),
|
||||
toc: s.boolean().default(true),
|
||||
source: NestedProperties.optional(),
|
||||
// mirrors contentlayer2's `_raw.flattenedPath`: file path relative to the
|
||||
// content dir, extension stripped, trailing `/index` dropped.
|
||||
path: s.path(),
|
||||
raw: s.raw(),
|
||||
// internal doc cross-links (e.g. `[Button](components/button)`) aren't
|
||||
// real files on disk — disable Velite's default asset-copying behavior,
|
||||
// which otherwise treats every relative link as a local file to copy.
|
||||
// Minification is also disabled: it's pure CPU-bound Terser work with no
|
||||
// real benefit for a dev-only content cache, and dominates build time.
|
||||
code: s.mdx({ copyLinkedFiles: false, minify: false }),
|
||||
})
|
||||
.transform(({ path: flattenedPath, ...data }) => ({
|
||||
...data,
|
||||
slug: `/${flattenedPath}`,
|
||||
slugAsParams: flattenedPath.split('/').slice(1).join('/'),
|
||||
}))
|
||||
|
||||
export default defineConfig({
|
||||
root: './content',
|
||||
output: {
|
||||
clean: true,
|
||||
},
|
||||
collections: {
|
||||
allDocs: {
|
||||
name: 'Doc',
|
||||
pattern: 'docs/**/*.mdx',
|
||||
schema: docs,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const ExploreItem = defineNestedType(() => ({
|
||||
name: 'ExploreItem',
|
||||
fields: {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
link: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
itemType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const CourseHero = defineNestedType(() => ({
|
||||
name: 'CourseHero',
|
||||
fields: {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
subtitle: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const NestedProperties = defineNestedType(() => ({
|
||||
name: 'NestedProperties',
|
||||
fields: {
|
||||
radix: {
|
||||
type: 'boolean',
|
||||
},
|
||||
shadcn: {
|
||||
type: 'boolean',
|
||||
},
|
||||
vaul: {
|
||||
type: 'boolean',
|
||||
},
|
||||
inputOtp: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reactAccessibleTreeview: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
export const Doc = defineDocumentType(() => ({
|
||||
name: 'Doc',
|
||||
filePathPattern: `**/*.mdx`,
|
||||
contentType: 'mdx',
|
||||
fields: {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
published: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
links: {
|
||||
type: 'nested',
|
||||
of: LinksProperties,
|
||||
},
|
||||
featured: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
component: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
fragment: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
toc: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
chapterNumber: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
},
|
||||
explore: {
|
||||
type: 'list',
|
||||
of: ExploreItem,
|
||||
required: false,
|
||||
},
|
||||
courseHero: {
|
||||
type: 'nested',
|
||||
of: CourseHero,
|
||||
required: false,
|
||||
},
|
||||
source: {
|
||||
type: 'nested',
|
||||
of: NestedProperties,
|
||||
},
|
||||
},
|
||||
computedFields,
|
||||
}))
|
||||
|
||||
export default makeSource({
|
||||
disableImportAliasWarning: true,
|
||||
contentDirPath: './content',
|
||||
documentTypes: [Doc],
|
||||
mdx: {
|
||||
remarkPlugins: [remarkGfm, codeImport],
|
||||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
rehypeComponent,
|
||||
() => (tree) => {
|
||||
visit(tree, (node) => {
|
||||
if (node?.type === 'element' && node?.tagName === 'pre') {
|
||||
@@ -190,12 +95,19 @@ export default makeSource({
|
||||
},
|
||||
[
|
||||
rehypePrettyCode,
|
||||
// rehypePrettyCodeOptions,
|
||||
{
|
||||
getHighlighter: async () => {
|
||||
const theme = await loadTheme(path.join(process.cwd(), '/lib/themes/supabase-2.json'))
|
||||
return await getHighlighter({ theme })
|
||||
},
|
||||
// Memoized so the (expensive) theme parse + oniguruma WASM init runs once
|
||||
// for the whole build, instead of once per file — velite compiles every
|
||||
// file concurrently, so without this every doc pays that cost redundantly.
|
||||
getHighlighter: (() => {
|
||||
let highlighterPromise
|
||||
return () => {
|
||||
highlighterPromise ??= loadTheme(
|
||||
path.join(process.cwd(), '/lib/themes/supabase-2.json')
|
||||
).then((theme) => getHighlighter({ theme }))
|
||||
return highlighterPromise
|
||||
}
|
||||
})(),
|
||||
onVisitLine(node) {
|
||||
// Prevent lines from collapsing in `display: grid` mode, and allow empty
|
||||
// lines to be copy/pasted
|
||||
@@ -240,7 +152,6 @@ export default makeSource({
|
||||
}
|
||||
})
|
||||
},
|
||||
// rehypeNpmCommand,
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
@@ -35,4 +35,5 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.contentlayer
|
||||
.contentlayer
|
||||
.velite
|
||||
@@ -5,19 +5,22 @@ import { ExploreMore } from '@/components/explore-more'
|
||||
import { Mdx } from '@/components/mdx-components'
|
||||
import { NextUp } from '@/components/next-up'
|
||||
import { DashboardTableOfContents } from '@/components/toc'
|
||||
import { getTableOfContents } from '@/lib/toc'
|
||||
import { getCurrentChapter } from '@/lib/get-current-chapter'
|
||||
import { getNextPage } from '@/lib/get-next-page'
|
||||
import { getTableOfContents } from '@/lib/toc'
|
||||
import { absoluteUrl, cn } from '@/lib/utils'
|
||||
|
||||
import '@/styles/code-block-variables.css'
|
||||
import '@/styles/mdx.css'
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import Balancer from 'react-wrap-balancer'
|
||||
import { ScrollArea } from 'ui'
|
||||
|
||||
import { allDocs } from '@/.velite'
|
||||
|
||||
interface DocPageProps {
|
||||
params: Promise<{
|
||||
slug: string[]
|
||||
@@ -75,17 +78,13 @@ export default async function DocPage(props: DocPageProps) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const toc = await getTableOfContents(doc.body.raw)
|
||||
const toc = await getTableOfContents(doc.raw)
|
||||
const nextPage = getNextPage(doc.slugAsParams)
|
||||
const currentChapter = getCurrentChapter(doc.slugAsParams)
|
||||
const slugSegments = doc.slugAsParams.split('/')
|
||||
const isIntroductionPage = slugSegments[slugSegments.length - 1] === 'introduction'
|
||||
|
||||
const exploreItems = (
|
||||
doc as {
|
||||
explore?: Array<{ title: string; link: string; itemType?: string; description?: string }>
|
||||
}
|
||||
).explore
|
||||
const exploreItems = doc.explore
|
||||
|
||||
return (
|
||||
<div className={cn('relative')}>
|
||||
@@ -121,7 +120,7 @@ export default async function DocPage(props: DocPageProps) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-12">
|
||||
<Mdx code={doc.body.code} />
|
||||
<Mdx code={doc.code} />
|
||||
</div>
|
||||
{exploreItems && exploreItems.length > 0 && <ExploreMore items={exploreItems} />}
|
||||
{currentChapter && nextPage && (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMDXComponent } from 'next-contentlayer2/hooks'
|
||||
import Link from 'next/link'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, cn } from 'ui'
|
||||
|
||||
import { Callout } from './callout'
|
||||
import { CopyButton } from './copy-button'
|
||||
import TanStackBeta from './tanstack-beta'
|
||||
import { useMDXComponent } from '@/lib/use-mdx-component'
|
||||
|
||||
const components = {
|
||||
Accordion,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
import { normalizeSlug } from './get-next-page'
|
||||
import { allDocs } from '@/.velite'
|
||||
|
||||
/**
|
||||
* Gets the current chapter number from a doc
|
||||
@@ -15,7 +15,7 @@ export function getCurrentChapter(
|
||||
}
|
||||
|
||||
// Get chapterNumber from frontmatter first, then fallback to parsing from title
|
||||
let chapterNumber: number | undefined = (doc as any)?.chapterNumber
|
||||
let chapterNumber: number | undefined = doc?.chapterNumber
|
||||
if (!chapterNumber) {
|
||||
// Try to extract chapter number from title (e.g., "2: CSS Styling" -> 2)
|
||||
const chapterMatch = doc.title.match(/^(\d+):/)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
import { allDocs } from '@/.velite'
|
||||
import { courses } from '@/config/docs'
|
||||
import { SidebarNavItem } from '@/types/nav'
|
||||
|
||||
@@ -65,8 +65,7 @@ export function getNextPage(
|
||||
const description = doc?.description || nextPage.title
|
||||
|
||||
// Get chapterNumber from frontmatter first, then fallback to parsing from title
|
||||
// Using type assertion since contentlayer types may need regeneration
|
||||
let chapterNumber: number | undefined = (doc as any)?.chapterNumber
|
||||
let chapterNumber: number | undefined = doc?.chapterNumber
|
||||
if (!chapterNumber) {
|
||||
// Try to extract chapter number from title (e.g., "2: CSS Styling" -> 2)
|
||||
const chapterMatch = nextPage.title.match(/^(\d+):/)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import * as runtime from 'react/jsx-runtime'
|
||||
|
||||
// Velite compiles MDX to a "function-body" (see velite.config.js `code: s.mdx()`)
|
||||
// that reads its jsx runtime off `arguments[0]` — mirrors the pattern from
|
||||
// https://velite.js.org/guide/using-mdx.
|
||||
const EMPTY_GLOBALS: Record<string, unknown> = {}
|
||||
|
||||
function getMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
const fn = new Function(code)
|
||||
return fn({ ...runtime, ...globals }).default
|
||||
}
|
||||
|
||||
export function useMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
return React.useMemo(() => getMDXComponent(code, globals), [code, globals])
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { withContentlayer } from 'next-contentlayer2'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ['ui', 'common', 'shared-data', 'icons', 'tsconfig'],
|
||||
@@ -32,4 +30,4 @@ const nextConfig = {
|
||||
},
|
||||
}
|
||||
|
||||
export default withContentlayer(nextConfig)
|
||||
export default nextConfig
|
||||
+10
-8
@@ -5,24 +5,24 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "next dev --port 3007",
|
||||
"build": "pnpm run content:build && next build --turbopack",
|
||||
"dev:content": "velite dev",
|
||||
"dev:next": "next dev --port 3007",
|
||||
"dev": "pnpm run build:content && run-p dev:content dev:next",
|
||||
"build": "pnpm run build:content && next build --turbopack",
|
||||
"build:content": "velite build --strict",
|
||||
"internal:sync": "tsx ./scripts/sync-internal-content.mts",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo",
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit"
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo .contentlayer .velite tsconfig.tsbuildinfo",
|
||||
"typecheck": "pnpm build:content && tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"common": "workspace:*",
|
||||
"contentlayer2": "0.4.6",
|
||||
"framer-motion": "^11.18.2",
|
||||
"jotai": "^2.8.0",
|
||||
"lucide-react": "*",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
@@ -35,7 +35,8 @@
|
||||
"remark-gfm": "^4.0.0",
|
||||
"ui": "workspace:*",
|
||||
"ui-patterns": "workspace:*",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"velite": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
@@ -43,6 +44,7 @@
|
||||
"@types/react": "catalog:",
|
||||
"config": "workspace:^",
|
||||
"mdast-util-toc": "^6.1.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"shiki": "^1.1.7",
|
||||
"tailwindcss": "catalog:",
|
||||
"tsconfig": "workspace:*",
|
||||
|
||||
@@ -5,19 +5,12 @@
|
||||
"paths": {
|
||||
// @deprecated: Don't import directly without suffix. Use @/ always.
|
||||
"*": ["./*"],
|
||||
"@/*": ["./*"],
|
||||
"contentlayer/generated": ["./.contentlayer/generated"]
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"plugins": [{ "name": "next" }],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": false
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".contentlayer/generated"
|
||||
],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".velite"],
|
||||
"exclude": ["node_modules", "./scripts/build-registry.mts"]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**/*", "!.next/dev/**/*"],
|
||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**/*", "!.next/dev/**/*", ".velite/**"],
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,111 +1,80 @@
|
||||
import path from 'path'
|
||||
import { getHighlighter, loadTheme } from '@shikijs/compat'
|
||||
import { defineDocumentType, defineNestedType, makeSource } from 'contentlayer2/source-files'
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
||||
import rehypePrettyCode from 'rehype-pretty-code'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import { codeImport } from 'remark-code-import'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { defineConfig, s } from 'velite'
|
||||
|
||||
/** @type {import('contentlayer2/source-files').ComputedFields} */
|
||||
const computedFields = {
|
||||
slug: {
|
||||
type: 'string',
|
||||
resolve: (doc) => `/${doc._raw.flattenedPath}`,
|
||||
},
|
||||
slugAsParams: {
|
||||
type: 'string',
|
||||
resolve: (doc) => doc._raw.flattenedPath.split('/').slice(1).join('/'),
|
||||
},
|
||||
}
|
||||
const LinksProperties = s.object({
|
||||
doc: s.string().optional(),
|
||||
api: s.string().optional(),
|
||||
})
|
||||
|
||||
const LinksProperties = defineNestedType(() => ({
|
||||
name: 'LinksProperties',
|
||||
fields: {
|
||||
doc: {
|
||||
type: 'string',
|
||||
},
|
||||
api: {
|
||||
type: 'string',
|
||||
const ExploreItem = s.object({
|
||||
title: s.string(),
|
||||
link: s.string(),
|
||||
itemType: s.string().optional(),
|
||||
description: s.string().optional(),
|
||||
})
|
||||
|
||||
const CourseHero = s.object({
|
||||
title: s.string(),
|
||||
subtitle: s.string(),
|
||||
description: s.string(),
|
||||
})
|
||||
|
||||
const NestedProperties = s.object({
|
||||
radix: s.boolean().optional(),
|
||||
shadcn: s.boolean().optional(),
|
||||
vaul: s.boolean().optional(),
|
||||
inputOtp: s.boolean().optional(),
|
||||
reactAccessibleTreeview: s.boolean().optional(),
|
||||
})
|
||||
|
||||
const docs = s
|
||||
.object({
|
||||
title: s.string(),
|
||||
description: s.string(),
|
||||
published: s.boolean().default(true),
|
||||
links: LinksProperties.optional(),
|
||||
featured: s.boolean().default(false),
|
||||
component: s.boolean().default(false),
|
||||
fragment: s.boolean().default(false),
|
||||
toc: s.boolean().default(true),
|
||||
chapterNumber: s.number().optional(),
|
||||
explore: s.array(ExploreItem).optional(),
|
||||
courseHero: CourseHero.optional(),
|
||||
source: NestedProperties.optional(),
|
||||
// mirrors contentlayer2's `_raw.flattenedPath`: file path relative to the
|
||||
// content dir, extension stripped, trailing `/index` dropped.
|
||||
path: s.path(),
|
||||
raw: s.raw(),
|
||||
// internal doc cross-links (e.g. `[Button](components/button)`) aren't
|
||||
// real files on disk — disable Velite's default asset-copying behavior,
|
||||
// which otherwise treats every relative link as a local file to copy.
|
||||
code: s.mdx({ copyLinkedFiles: false }),
|
||||
})
|
||||
.transform(({ path: flattenedPath, ...data }) => ({
|
||||
...data,
|
||||
slug: `/${flattenedPath}`,
|
||||
slugAsParams: flattenedPath,
|
||||
}))
|
||||
|
||||
export default defineConfig({
|
||||
root: './content',
|
||||
output: {
|
||||
clean: true,
|
||||
},
|
||||
collections: {
|
||||
allDocs: {
|
||||
name: 'Doc',
|
||||
pattern: '**/*.mdx',
|
||||
schema: docs,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const NestedProperties = defineNestedType(() => ({
|
||||
name: 'NestedProperties',
|
||||
fields: {
|
||||
radix: {
|
||||
type: 'boolean',
|
||||
},
|
||||
shadcn: {
|
||||
type: 'boolean',
|
||||
},
|
||||
vaul: {
|
||||
type: 'boolean',
|
||||
},
|
||||
inputOtp: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reactAccessibleTreeview: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
export const Doc = defineDocumentType(() => ({
|
||||
name: 'Doc',
|
||||
filePathPattern: `docs/**/*.mdx`,
|
||||
contentType: 'mdx',
|
||||
fields: {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
published: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
links: {
|
||||
type: 'nested',
|
||||
of: LinksProperties,
|
||||
},
|
||||
featured: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
component: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
fragment: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
toc: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
source: {
|
||||
type: 'nested',
|
||||
of: NestedProperties,
|
||||
},
|
||||
},
|
||||
computedFields,
|
||||
}))
|
||||
|
||||
export default makeSource({
|
||||
contentDirPath: './content',
|
||||
disableImportAliasWarning: true,
|
||||
documentTypes: [Doc],
|
||||
mdx: {
|
||||
remarkPlugins: [remarkGfm, codeImport],
|
||||
rehypePlugins: [
|
||||
@@ -136,7 +105,6 @@ export default makeSource({
|
||||
},
|
||||
[
|
||||
rehypePrettyCode,
|
||||
// rehypePrettyCodeOptions,
|
||||
{
|
||||
getHighlighter: async () => {
|
||||
const theme = await loadTheme(path.join(process.cwd(), '/lib/themes/supabase-2.json'))
|
||||
@@ -186,7 +154,6 @@ export default makeSource({
|
||||
}
|
||||
})
|
||||
},
|
||||
// rehypeNpmCommand,
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
@@ -770,6 +770,25 @@ export default defineConfig(({ command, mode }) => {
|
||||
// with `c is not a function` because its `styleHandler` import
|
||||
// came in through the now-too-large `lucide-react` chunk). Pin
|
||||
// React explicitly so it stays a leaf vendor chunk.
|
||||
//
|
||||
// `packages/ui/src/components/shadcn/ui/field.tsx` — its only
|
||||
// non-barrel importer is Storage's `FileExplorerHeader`, so
|
||||
// Rolldown pools it into the storage bucket page chunk while the
|
||||
// `ui` package barrel (`packages/ui/index.tsx`) re-exports it —
|
||||
// `ui` ends up importing `FieldDescription` back from the page
|
||||
// chunk it's itself imported by.
|
||||
//
|
||||
// `packages/ui/src/components/shadcn/ui/drawer.tsx` — same shape,
|
||||
// its only non-barrel importer sits inside the Logs Explorer page
|
||||
// tree (`DataTableFilterControlsDrawer`), so it gets pooled into
|
||||
// the `logs` page chunk while `ui`'s barrel re-exports it too.
|
||||
//
|
||||
// `packages/ui/src/components/shadcn/ui/form.tsx` and
|
||||
// `packages/ui/src/components/shadcn/ui/sidebar.tsx` (+
|
||||
// `use-mobile.tsx`) — same shape again: each gets pooled into
|
||||
// whichever page/feature chunk happens to be its only non-barrel
|
||||
// importer (a form page, `components/interfaces/Sidebar.tsx`)
|
||||
// while `ui`'s barrel re-exports them too.
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
@@ -791,6 +810,21 @@ export default defineConfig(({ command, mode }) => {
|
||||
if (id.includes('node_modules/lucide-react/')) {
|
||||
return 'lucide-react'
|
||||
}
|
||||
if (id.includes('packages/ui/src/components/shadcn/ui/field.tsx')) {
|
||||
return 'ui-field'
|
||||
}
|
||||
if (id.includes('packages/ui/src/components/shadcn/ui/drawer.tsx')) {
|
||||
return 'ui-drawer'
|
||||
}
|
||||
if (id.includes('packages/ui/src/components/shadcn/ui/form.tsx')) {
|
||||
return 'ui-form'
|
||||
}
|
||||
if (
|
||||
id.includes('packages/ui/src/components/shadcn/ui/sidebar.tsx') ||
|
||||
id.includes('packages/ui/src/components/hooks/use-mobile.tsx')
|
||||
) {
|
||||
return 'ui-sidebar'
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
.contentlayer
|
||||
.velite
|
||||
|
||||
# Auto-generated on every Vercel build by `pnpm build:llms` -> scripts/build-llms-txt.ts
|
||||
public/llms.txt
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import '@/styles/code-block-variables.css'
|
||||
import '@/styles/mdx.css'
|
||||
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import Balancer from 'react-wrap-balancer'
|
||||
import { ScrollArea } from 'ui'
|
||||
|
||||
import { allDocs } from '@/.velite'
|
||||
import { metadata as mainMetadata } from '@/app/layout'
|
||||
import { FrameworkSelector } from '@/components/framework-selector'
|
||||
import { Mdx } from '@/components/mdx-components'
|
||||
@@ -77,7 +77,7 @@ export default async function DocPage(props: DocPageProps) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const toc = await getTableOfContents(doc.body.raw)
|
||||
const toc = await getTableOfContents(doc.raw)
|
||||
|
||||
return (
|
||||
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] px-8 md:px-16 py-20">
|
||||
@@ -100,7 +100,7 @@ export default async function DocPage(props: DocPageProps) {
|
||||
</div>
|
||||
<SourcePanel doc={doc} />
|
||||
<div className="pb-12">
|
||||
<Mdx code={doc.body.code} />
|
||||
<Mdx code={doc.code} />
|
||||
</div>
|
||||
</div>
|
||||
{doc.toc && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useMDXComponent } from 'next-contentlayer2/hooks'
|
||||
import Link from 'next/link'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, cn } from 'ui'
|
||||
|
||||
@@ -14,6 +13,7 @@ import { RegistryBlock } from './registry-block'
|
||||
import { StyleWrapper } from './style-wrapper'
|
||||
import TanStackBeta from './tanstack-beta'
|
||||
import { TanstackDBGenerator } from './tanstack-db-generator'
|
||||
import { useMDXComponent } from '@/lib/use-mdx-component'
|
||||
import type { Style } from '@/registry/styles'
|
||||
|
||||
const components = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from 'next/link'
|
||||
import { forwardRef } from 'react'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { Doc } from '@/.contentlayer/generated'
|
||||
import { Doc } from '@/.velite'
|
||||
|
||||
const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> & { doc: Doc }>(
|
||||
({ doc, children, ...props }, ref) => {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import * as runtime from 'react/jsx-runtime'
|
||||
|
||||
// Velite compiles MDX to a "function-body" (see velite.config.js `code: s.mdx()`)
|
||||
// that reads its jsx runtime off `arguments[0]` — mirrors the pattern from
|
||||
// https://velite.js.org/guide/using-mdx.
|
||||
const EMPTY_GLOBALS: Record<string, unknown> = {}
|
||||
|
||||
function getMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
const fn = new Function(code)
|
||||
return fn({ ...runtime, ...globals }).default
|
||||
}
|
||||
|
||||
export function useMDXComponent(code: string, globals: Record<string, unknown> = EMPTY_GLOBALS) {
|
||||
return React.useMemo(() => getMDXComponent(code, globals), [code, globals])
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { withContentlayer } from 'next-contentlayer2'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ['ui', 'common', 'shared-data', 'icons', 'tsconfig'],
|
||||
@@ -37,4 +35,4 @@ const nextConfig = {
|
||||
},
|
||||
}
|
||||
|
||||
export default withContentlayer(nextConfig)
|
||||
export default nextConfig
|
||||
@@ -5,21 +5,22 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "next dev --port 3004",
|
||||
"predev": "pnpm run build:markdown",
|
||||
"build": "pnpm run content:build && pnpm run build:registry && pnpm run build:markdown && pnpm run build:llms && pnpm run build:app",
|
||||
"dev:content": "velite dev",
|
||||
"dev:next": "next dev --port 3004",
|
||||
"dev": "run-p build:content build:registry build:markdown && run-p --race dev:*",
|
||||
"build:content": "velite build --strict",
|
||||
"build:registry": "rimraf -G public/r/* && tsx ./scripts/build-registry.mts && shadcn build public/r/registry.json && tsx scripts/clean-registry.ts",
|
||||
"build:markdown": "tsx ./scripts/build-markdown.ts",
|
||||
"build:llms": "tsx ./scripts/build-llms-txt.ts",
|
||||
"build:next": "next build --turbopack",
|
||||
"build": "run-p build:content build:registry build:llms build:markdown && pnpm build:next",
|
||||
"test:markdown": "tsx --test scripts/library-mdx-to-markdown.test.ts",
|
||||
"test:middleware": "pnpm run build:markdown && tsx --test middleware.test.ts",
|
||||
"build:app": "next build --turbopack",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo",
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo .contentlayer .velite",
|
||||
"typecheck": "pnpm build:content && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
@@ -37,7 +38,6 @@
|
||||
"cmdk": "^1.1.1",
|
||||
"common": "workspace:*",
|
||||
"common-tags": "^1.8.2",
|
||||
"contentlayer2": "0.4.6",
|
||||
"eslint-config-supabase": "workspace:*",
|
||||
"framer-motion": "^11.18.2",
|
||||
"icons": "workspace:*",
|
||||
@@ -45,7 +45,6 @@
|
||||
"lucide-react": "*",
|
||||
"monaco-editor": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "catalog:",
|
||||
"openai": "^5.9.0",
|
||||
"openapi-fetch": "0.12.4",
|
||||
@@ -67,6 +66,7 @@
|
||||
"ui-patterns": "workspace:*",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vaul": "^1.1.2",
|
||||
"velite": "catalog:",
|
||||
"y-monaco": "^0.1.6",
|
||||
"y-protocols": "^1.0.7",
|
||||
"yjs": "^13.6.29",
|
||||
@@ -97,6 +97,7 @@
|
||||
"mdast-util-toc": "^6.1.1",
|
||||
"micromark-extension-gfm": "^2.0.3",
|
||||
"micromark-extension-mdxjs": "^1.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "catalog:",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-router": "^7.13.2",
|
||||
|
||||
@@ -5,18 +5,11 @@
|
||||
"allowJs": false,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"contentlayer/generated": ["./.contentlayer/generated"]
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"plugins": [{ "name": "next" }]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".contentlayer/generated"
|
||||
],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".velite"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./scripts/build-registry.mts",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
".next/**",
|
||||
"!.next/cache/**",
|
||||
"!.next/dev/**/*",
|
||||
".contentlayer/**",
|
||||
".velite/**",
|
||||
"public/markdown/**",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,122 +1,68 @@
|
||||
import path from 'path'
|
||||
import { getHighlighter, loadTheme } from '@shikijs/compat'
|
||||
import { defineDocumentType, defineNestedType, makeSource } from 'contentlayer2/source-files'
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
||||
import rehypePrettyCode from 'rehype-pretty-code'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import { codeImport } from 'remark-code-import'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { defineConfig, s } from 'velite'
|
||||
|
||||
import { rehypeComponent } from './lib/rehype-component'
|
||||
import { rehypeNpmCommand } from './lib/rehype-npm-command'
|
||||
const LinksProperties = s.object({
|
||||
doc: s.string().optional(),
|
||||
api: s.string().optional(),
|
||||
})
|
||||
|
||||
/** @type {import('contentlayer2/source-files').ComputedFields} */
|
||||
const computedFields = {
|
||||
slug: {
|
||||
type: 'string',
|
||||
resolve: (doc) => `/${doc._raw.flattenedPath}`,
|
||||
const NestedProperties = s.object({
|
||||
radix: s.boolean().optional(),
|
||||
shadcn: s.boolean().optional(),
|
||||
vaul: s.boolean().optional(),
|
||||
inputOtp: s.boolean().optional(),
|
||||
reactAccessibleTreeview: s.boolean().optional(),
|
||||
})
|
||||
|
||||
const docs = s
|
||||
.object({
|
||||
title: s.string(),
|
||||
description: s.string(),
|
||||
published: s.boolean().default(true),
|
||||
links: LinksProperties.optional(),
|
||||
featured: s.boolean().default(false),
|
||||
component: s.boolean().default(false),
|
||||
fragment: s.boolean().default(false),
|
||||
toc: s.boolean().default(true),
|
||||
source: NestedProperties.optional(),
|
||||
// mirrors contentlayer2's `_raw.flattenedPath`: file path relative to the
|
||||
// content dir, extension stripped, trailing `/index` dropped.
|
||||
path: s.path(),
|
||||
raw: s.raw(),
|
||||
// internal doc cross-links (e.g. `[Button](components/button)`) aren't
|
||||
// real files on disk — disable Velite's default asset-copying behavior,
|
||||
// which otherwise treats every relative link as a local file to copy.
|
||||
code: s.mdx({ copyLinkedFiles: false }),
|
||||
})
|
||||
.transform(({ path: flattenedPath, ...data }) => ({
|
||||
...data,
|
||||
slug: `/${flattenedPath}`,
|
||||
slugAsParams: flattenedPath.split('/').slice(1).join('/'),
|
||||
}))
|
||||
|
||||
export default defineConfig({
|
||||
root: './content',
|
||||
output: {
|
||||
clean: true,
|
||||
},
|
||||
slugAsParams: {
|
||||
type: 'string',
|
||||
resolve: (doc) => doc._raw.flattenedPath.split('/').slice(1).join('/'),
|
||||
},
|
||||
}
|
||||
|
||||
const LinksProperties = defineNestedType(() => ({
|
||||
name: 'LinksProperties',
|
||||
fields: {
|
||||
doc: {
|
||||
type: 'string',
|
||||
},
|
||||
api: {
|
||||
type: 'string',
|
||||
collections: {
|
||||
allDocs: {
|
||||
name: 'Doc',
|
||||
pattern: 'docs/**/*.mdx',
|
||||
schema: docs,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const NestedProperties = defineNestedType(() => ({
|
||||
name: 'NestedProperties',
|
||||
fields: {
|
||||
radix: {
|
||||
type: 'boolean',
|
||||
},
|
||||
shadcn: {
|
||||
type: 'boolean',
|
||||
},
|
||||
vaul: {
|
||||
type: 'boolean',
|
||||
},
|
||||
inputOtp: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reactAccessibleTreeview: {
|
||||
type: 'boolean',
|
||||
},
|
||||
recharts: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
export const Doc = defineDocumentType(() => ({
|
||||
name: 'Doc',
|
||||
filePathPattern: `docs/**/*.mdx`,
|
||||
contentType: 'mdx',
|
||||
fields: {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
published: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
links: {
|
||||
type: 'nested',
|
||||
of: LinksProperties,
|
||||
},
|
||||
featured: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
component: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
fragment: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
toc: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
source: {
|
||||
type: 'nested',
|
||||
of: NestedProperties,
|
||||
},
|
||||
},
|
||||
computedFields,
|
||||
}))
|
||||
|
||||
export default makeSource({
|
||||
contentDirPath: './content',
|
||||
disableImportAliasWarning: true,
|
||||
documentTypes: [Doc],
|
||||
mdx: {
|
||||
remarkPlugins: [remarkGfm, codeImport],
|
||||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
rehypeComponent,
|
||||
() => (tree) => {
|
||||
visit(tree, (node) => {
|
||||
if (node?.type === 'element' && node?.tagName === 'pre') {
|
||||
@@ -143,7 +89,6 @@ export default makeSource({
|
||||
},
|
||||
[
|
||||
rehypePrettyCode,
|
||||
// rehypePrettyCodeOptions,
|
||||
{
|
||||
getHighlighter: async () => {
|
||||
const theme = await loadTheme(path.join(process.cwd(), '/lib/themes/supabase-2.json'))
|
||||
@@ -193,7 +138,6 @@ export default makeSource({
|
||||
}
|
||||
})
|
||||
},
|
||||
// rehypeNpmCommand,
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
@@ -106,6 +106,7 @@ module.exports = defineConfig([
|
||||
'vitest.config.ts',
|
||||
'postcss.config.js',
|
||||
'postcss.config.cjs',
|
||||
'velite.config.js',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-exports': [
|
||||
|
||||
@@ -13,7 +13,6 @@ export * from './src/components/NavMenu'
|
||||
export * from './src/components/SidePanel'
|
||||
|
||||
// HTML
|
||||
|
||||
export {
|
||||
Heading,
|
||||
getAnchor,
|
||||
|
||||
Generated
+545
-1521
File diff suppressed because it is too large.
Load diff
@@ -49,6 +49,7 @@ catalog:
|
||||
typescript: ~6.0.2
|
||||
valtio: ^2.3.2
|
||||
vite: ^8.0.16
|
||||
velite: ^0.4.0
|
||||
vite-tsconfig-paths: ^6.1.1
|
||||
vitest: ^4.1.4
|
||||
zod: 3.25.76
|
||||
|
||||
Reference in new issue
Block a user