diff --git a/.github/workflows/library-tests.yml b/.github/workflows/library-tests.yml index 9eca55ab6e4..48c68a4236f 100644 --- a/.github/workflows/library-tests.yml +++ b/.github/workflows/library-tests.yml @@ -51,7 +51,7 @@ jobs: - run: pnpm --filter library build:registry - name: Check generated registry run: | - registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r)" + registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r apps/ui-library/__registry__)" if [ -n "$registry_changes" ]; then printf '%s\n' "$registry_changes" echo 'Run pnpm --filter library build:registry and commit the generated registry files.' diff --git a/apps/ui-library/README.md b/apps/ui-library/README.md index 2d77ceb73ff..a95c91d69c9 100644 --- a/apps/ui-library/README.md +++ b/apps/ui-library/README.md @@ -1,44 +1,49 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Supabase Library -## Getting Started +The library is the documentation and shadcn registry app for Supabase blocks and starter apps. Its workspace package name is `library`. -First, run the development server: +## Development and checks + +Run these commands from the repository root with the repository's Node and pnpm versions: ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +pnpm --filter library dev +pnpm --filter library test +pnpm --filter library typecheck +pnpm --filter library lint +pnpm --filter library build ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +The development server runs at [localhost:3004/library](http://localhost:3004/library). `build` and `dev` build the registry first, then generate the HTML content, Markdown guides, and `llms.txt`. Markdown generation reads the completed registry, so these steps must retain that order. Type checking generates Next.js types before running TypeScript and works without a previous development build. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Registry JSON in `public/r` and the preview index in `__registry__` are generated and committed. Regenerate them with `pnpm --filter library build:registry`; do not edit them by hand. `.velite`, `public/markdown`, and `public/llms.txt` are generated build artifacts. Library CI runs the test suite, checks for registry drift, and builds the app. -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +## Authoring guides -## Learn More +Write documentation in `content/docs//.mdx`, or `content/docs/starters/.mdx` for starter apps. Keep frontmatter for page metadata. Installation headings, prerequisites, commands, and follow-up instructions belong in the MDX body, in the order readers should follow them. -To learn more about Next.js, take a look at the following resources: +Use `BlockItem` for a registry installation command. Its name is the published registry item ID. React is the default; Vue and Nuxt pages must select the Vue CLI explicitly: -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +```mdx +## Installation -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + -## Deploy on Vercel + +``` -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +The shared command helper supplies both the page and its Markdown export. Vue commands use an absolute registry URL so a fresh project does not need an `@supabase` alias. Put other commands in ordinary fenced shell blocks. Keep notes next to the command they explain. Use `showOpenInV0` on `BlockItem` when a block supports that action. For Vue and Nuxt blocks that need an existing Supabase client, link the client guide and include a conditional client-install command; let readers reuse an existing client. -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +The primary Copy prompt action points agents to the canonical `/library/docs//.md` guide and asks them to prefer the guide's shadcn install command over copying files from registry JSON. It does not repeat installation commands. Starter prompts describe creating a new application; block prompts describe integrating into the existing project. The page also exposes a Markdown link. + +The Markdown exporter reads the same MDX body, expands registry commands and file trees, and links each block's registry JSON as its source. Instruction-bearing MDX components need an explicit Markdown handler and a fixture in `lib/library-mdx-to-markdown.test.ts`. Missing registry files, document references, or unsupported components fail generation. + +Register discoverable blocks and starter apps in `config/library.ts` and the existing navigation definitions in `config/docs.ts`. The catalog test compares these routes with the actual content directory. Keep intentional omissions explicit in that test. ## Supabase types -To regenerate the Supabase database types, run +From this directory, regenerate local database types with: -``` +```bash supabase gen types --local > registry/default/fixtures/database.types.ts ``` diff --git a/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx b/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx index 60489518fce..5713413957b 100644 --- a/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx +++ b/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx @@ -1,20 +1,21 @@ import '@/styles/code-block-variables.css' import '@/styles/mdx.css' +import '@/styles/library-doc.css' -import { ChevronRight } from 'lucide-react' +import { ArrowRight, Blocks } from 'lucide-react' import type { Metadata } from 'next' +import Link from 'next/link' 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 { CopyDocPrompt } from '@/components/copy-doc-prompt' import { FrameworkSelector } from '@/components/framework-selector' import { Mdx } from '@/components/mdx-components' import { OpenInV0Button } from '@/components/open-in-v0-button' import { SourcePanel } from '@/components/source-panel' -import { DashboardTableOfContents } from '@/components/toc' -import { getTableOfContents } from '@/lib/toc' +import { libraryBlocks } from '@/config/library' import { absoluteUrl, cn } from '@/lib/utils' interface DocPageProps { @@ -78,46 +79,80 @@ export default async function DocPage(props: DocPageProps) { notFound() } - const toc = await getTableOfContents(doc.raw) + const isGuide = doc.slugAsParams.startsWith('getting-started/') + const isStarter = doc.slugAsParams.startsWith('starters/') + const libraryBlock = libraryBlocks.find((block) => block.href === `/docs/${doc.slugAsParams}`) + const markdownPath = `${process.env.NEXT_PUBLIC_BASE_PATH ?? '/library'}/docs/${doc.slugAsParams}.md` return ( -
-
-
-
Docs
- -
{doc.title}
-
-
-
-

{doc.title}

- {doc.description && ( -

- {doc.description} -

+
+
+ {!isGuide && ( +
+ + + {isStarter ? 'Starter' : 'Block'} + + {libraryBlock?.frameworkLabel ? ( + + {libraryBlock.frameworkLabel} + + ) : ( + )}
-
- + )} +

+ {doc.title} +

+ {doc.description && ( +

+ {doc.description} +

+ )} + {!isGuide && ( +
+ {doc.v0Name && }
-
+ )} +
+ +
-
- -
- {doc.toc && ( -
-
- -
- -
-
-
+ {doc.preview && ( +
+
)} +
+ +
+
+ + + Explore next + Browse all library blocks + + + +
) } diff --git a/apps/ui-library/app/(app)/layout.tsx b/apps/ui-library/app/(app)/layout.tsx index 57ef5793b4d..639d411298b 100644 --- a/apps/ui-library/app/(app)/layout.tsx +++ b/apps/ui-library/app/(app)/layout.tsx @@ -1,6 +1,6 @@ import { TelemetryWrapper } from './telemetry-wrapper' -import { Sidebar } from '@/components/sidebar' import { SiteFooter } from '@/components/site-footer' +import { SiteHeader } from '@/components/site-header' interface AppLayoutProps { children: React.ReactNode @@ -9,20 +9,11 @@ interface AppLayoutProps { export default function AppLayout({ children }: AppLayoutProps) { return ( <> - {/* main container */} -
- {/* main content */} -
- {/* {children} */} -
-
- -
-
{children}
-
-
-
-
+ +
+
+ {children} +
diff --git a/apps/ui-library/app/(app)/page.tsx b/apps/ui-library/app/(app)/page.tsx index a5e76aa08de..5083e266141 100644 --- a/apps/ui-library/app/(app)/page.tsx +++ b/apps/ui-library/app/(app)/page.tsx @@ -1,211 +1,11 @@ -import Link from 'next/link' -import { Button_Shadcn_ } from 'ui' +import { Suspense } from 'react' -import { BlockPreview } from '@/components/block-preview' -import { ComponentPreview } from '@/components/component-preview' - -// Horizontal grid line component -const HorizontalGridLine = () =>
- -export const dynamic = 'force-dynamic' -export const revalidate = 0 +import { LibraryOverview } from '@/components/library-overview' export default function Home() { - const roomName = `room-${Math.floor(Math.random() * 1000)}` - return ( -
-
- {/* Component Showcase with Grid */} -
- {/* Grid Container */} -
- {/* Grid Lines - Vertical (Columns) */} - {Array.from({ length: 13 }).map((_, i) => ( -
- ))} - - {/* Grid Content */} -
- {/* Heading Section */} -
-
-
-

- Blocks for Supabase Projects -

-

- A collection of components, blocks, and developer tools that connect your - project to Supabase via a single command. -

-
- - Get Started - - - Install Skills - -
-
-
-
- - {/* Password-based Authentication */} -
- Password-based Authentication - - Go to block ➔ - -
- -
-
- -
-
- - - {/* Social Authentication */} -
- Social Authentication - - Go to block ➔ - -
- -
-
- -
-
- - - {/* Realtime Cursors */} -
- Realtime Cursors - - Go to block ➔ - -
- -
-
- - -
-
- - - {/* Dropzone */} -
- File Upload - - Go to block ➔ - -
- -
-
- -
-
- - - {/* Current User Avatar */} -
- Current User Avatar - - Go to block ➔ - -
- -
-
- -
-
- - - {/* Realtime Avatar Stack */} -
- Realtime Avatar Stack - - Go to block ➔ - -
- -
-
- -
-
- - - {/* Realtime Chat */} -
- Realtime Chat - - Go to block ➔ - -
- -
-
- - -
-
- - - {/* Infinite Query Hook */} -
- Infinite Query Hook - - Go to block ➔ - -
- -
-
- -
-
- -
-
-
-
-
+ + + ) } diff --git a/apps/ui-library/app/example/base-injector.tsx b/apps/ui-library/app/example/base-injector.tsx index ae29b2484c1..2e71c2b4fcd 100644 --- a/apps/ui-library/app/example/base-injector.tsx +++ b/apps/ui-library/app/example/base-injector.tsx @@ -4,6 +4,8 @@ import { useEffect } from 'react' export function BaseInjector() { useEffect(() => { + document.documentElement.classList.add('preview') + // Remove any existing base tag const existingBase = document.querySelector('base') if (existingBase) existingBase.remove() @@ -13,14 +15,9 @@ export function BaseInjector() { base.href = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/example/password-based-auth/` document.head.prepend(base) - // Add preview class to body to use shadcn theme vars - const bodyElement = document.querySelector('body') - if (bodyElement) { - bodyElement.classList.add('preview') - } - return () => { base.remove() + document.documentElement.classList.remove('preview') } }, []) diff --git a/apps/ui-library/app/example/infinite-list-demo/layout.tsx b/apps/ui-library/app/example/infinite-list-demo/layout.tsx index 2a8bc579e57..c10de8295ba 100644 --- a/apps/ui-library/app/example/infinite-list-demo/layout.tsx +++ b/apps/ui-library/app/example/infinite-list-demo/layout.tsx @@ -11,7 +11,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( <> -
+
{children}
diff --git a/apps/ui-library/app/example/oauth-consent/layout.tsx b/apps/ui-library/app/example/oauth-consent/layout.tsx index bb6c1d2d25c..03ce787db6f 100644 --- a/apps/ui-library/app/example/oauth-consent/layout.tsx +++ b/apps/ui-library/app/example/oauth-consent/layout.tsx @@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( <> -
+
{children}
diff --git a/apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx index b265cabf808..a97a6111da5 100644 --- a/apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx +++ b/apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx @@ -13,7 +13,9 @@ export default function Page() {
- Thank you for signing up! + + Thank you for signing up! + Check your email to confirm diff --git a/apps/ui-library/app/example/password-based-auth/layout.tsx b/apps/ui-library/app/example/password-based-auth/layout.tsx index ea9f98a6c76..2684a9a64f2 100644 --- a/apps/ui-library/app/example/password-based-auth/layout.tsx +++ b/apps/ui-library/app/example/password-based-auth/layout.tsx @@ -1,7 +1,6 @@ import { Metadata } from 'next' import { BaseInjector } from './../base-injector' -import { Providers } from '@/app/Providers' export const metadata: Metadata = { title: 'Password Based Auth Example', @@ -9,34 +8,12 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - - -