mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(library): redesign the site around the block catalog (#50372)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature — the visual redesign itself. Part 5 of 6 in a stack that splits the library redesign into reviewable pieces. The four PRs beneath it carry the build, content and Markdown work; what's left here is layout, navigation and styling. ## What is the current behavior? The library is laid out like a documentation site: a sidebar tree of framework folders, a homepage that lists links, and a guide page that opens with prose. That shape suits reference material, but the library's job is to help someone find a block and install it — and the sidebar is the only way to discover one. ## What is the new behavior? The homepage is the catalog itself — blocks grouped by what they do (authentication, database, storage, realtime, messaging, AI, foundations) rather than by framework, each with a preview of what it renders, filterable by category. Navigation moves into a site header whose Explore menu opens the same categories, so the catalog is reachable from any page and the per-page sidebar tree is gone. A guide opens with what the reader came for: the block's name, the install command, and a preview pane with tabs — the running component and its files — before any prose. The file tree that used to sit mid-page under "Folder structure" is one of those tabs. Every guide also offers a copy of the agent prompt that points at its Markdown. Getting-started pages get the same treatment: the quickstart is now a framework-tabbed walkthrough rather than a wall of setup links. ## Additional context `BlockOverviewTabs` renders Preview and Files here. #50369, stacked on top of this one, adds the third "What's added" tab — it is the only part of the redesign that depends on the new resource analyzer, which is why it sits above this PR rather than below it. Also removes what the redesign orphaned: the table-of-contents component and its `remark` / `mdast-util-toc` dependencies, and the sidebar nav and command-item configuration the new header replaced. The block source changes are typography only — auth card titles move from `text-2xl` to `font-medium text-lg tracking-normal` — which is what regenerates the auth registry artifacts. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a redesigned Supabase Library catalog with categorized blocks, framework-aware navigation, previews, file views, and installation actions. * Added framework-specific quickstart guides for Next.js, React, Vue, Nuxt, React Router, and TanStack Start. * Added copy-to-clipboard prompts, “Open in v0” actions, starter templates, and richer visual previews. * **Improvements** * Updated documentation layouts, FAQ content, typography, navigation, accessibility, and responsive behavior. * Improved mobile navigation, framework selection, and standardized block installation guidance. * Refined authentication and social-login block presentation with more consistent heading styles. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
This commit is contained in:
161 files changed
+2654
-1947
No files matched your search
@@ -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.'
|
||||
|
||||
+28
-23
@@ -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/<framework>/<slug>.mdx`, or `content/docs/starters/<slug>.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!
|
||||
<BlockItem name="password-based-auth-nextjs" />
|
||||
|
||||
## Deploy on Vercel
|
||||
<BlockItem name="infinite-query-composable" framework="vue" />
|
||||
```
|
||||
|
||||
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/<framework>/<slug>.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
|
||||
```
|
||||
@@ -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 (
|
||||
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] px-8 md:px-16 py-20">
|
||||
<div className="mx-auto w-full min-w-0 max-w-4xl">
|
||||
<div className="mb-4 flex items-center space-x-1 text-sm text-foreground-muted">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">Docs</div>
|
||||
<ChevronRight className="h-4 w-4 text-foreground-muted" />
|
||||
<div className="text-foreground-lighter">{doc.title}</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row lg:items-end justify-between mb-5">
|
||||
<div className="space-y-2">
|
||||
<h1 className={cn('scroll-m-20 text-2xl lg:text-4xl tracking-tight')}>{doc.title}</h1>
|
||||
{doc.description && (
|
||||
<p className="text-base lg:text-lg text-foreground-light">
|
||||
<Balancer>{doc.description}</Balancer>
|
||||
</p>
|
||||
<main className="isolate px-4 pb-10 pt-4 md:px-8 md:pb-16 md:pt-6">
|
||||
<header
|
||||
className={cn(
|
||||
'relative z-20 flex flex-col gap-5 py-8 md:py-12',
|
||||
isGuide ? 'mx-auto max-w-2xl items-start text-left' : 'items-center text-center'
|
||||
)}
|
||||
>
|
||||
{!isGuide && (
|
||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||
<span className="inline-flex h-7 items-center gap-1.5 rounded-md border bg-surface-75 px-2 text-xs text-foreground-light">
|
||||
<Blocks size={12} />
|
||||
{isStarter ? 'Starter' : 'Block'}
|
||||
</span>
|
||||
{libraryBlock?.frameworkLabel ? (
|
||||
<span className="inline-flex h-7 items-center rounded-md border bg-surface-75 px-2 text-xs text-foreground-light">
|
||||
{libraryBlock.frameworkLabel}
|
||||
</span>
|
||||
) : (
|
||||
<FrameworkSelector />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<FrameworkSelector />
|
||||
)}
|
||||
<h1 className="-mb-2 max-w-2xl scroll-m-24 text-balance font-heading text-4xl font-medium leading-tight tracking-tight sm:text-5xl sm:leading-none">
|
||||
{doc.title}
|
||||
</h1>
|
||||
{doc.description && (
|
||||
<p className="max-w-xl text-balance text-lg font-medium text-foreground-muted">
|
||||
<Balancer>{doc.description}</Balancer>
|
||||
</p>
|
||||
)}
|
||||
{!isGuide && (
|
||||
<div className="flex flex-wrap items-start justify-center gap-2">
|
||||
<CopyDocPrompt
|
||||
title={doc.title}
|
||||
markdownPath={markdownPath}
|
||||
intent={isStarter ? 'create-app' : 'add-to-project'}
|
||||
/>
|
||||
{doc.v0Name && <OpenInV0Button name={doc.v0Name} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<SourcePanel doc={doc} />
|
||||
<div className="pb-12">
|
||||
<Mdx code={doc.code} />
|
||||
</div>
|
||||
</div>
|
||||
{doc.toc && (
|
||||
<div className="hidden text-sm xl:block">
|
||||
<div className="sticky top-16 -mt-10 pt-4">
|
||||
<ScrollArea className="pb-10">
|
||||
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
|
||||
<DashboardTableOfContents toc={toc} />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
{doc.preview && (
|
||||
<div className="library-doc-preview mx-auto mb-16 max-w-5xl">
|
||||
<Mdx code={doc.preview} />
|
||||
</div>
|
||||
)}
|
||||
<article className="library-doc-content relative z-0 min-w-0 pb-12">
|
||||
<Mdx code={doc.code} />
|
||||
</article>
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<Link
|
||||
href="/"
|
||||
className="group flex w-full items-center justify-between border-t py-8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<span className="text-left">
|
||||
<span className="block text-xs text-foreground-lighter">Explore next</span>
|
||||
<span className="mt-2 block text-sm">Browse all library blocks</span>
|
||||
</span>
|
||||
<ArrowRight
|
||||
size={16}
|
||||
className="text-foreground-lighter transition-transform group-hover:translate-x-1"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -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 */}
|
||||
<div className="pt-10 md:pt-0">
|
||||
{/* main content */}
|
||||
<main className="flex-1 max-w-site mx-auto w-full p-0">
|
||||
{/* {children} */}
|
||||
<div className="border-b">
|
||||
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] lg:grid-cols-[240px_minmax(0,1fr)]">
|
||||
<Sidebar />
|
||||
<div vaul-drawer-wrapper="">
|
||||
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<SiteHeader />
|
||||
<div className="border-b">
|
||||
<div id="main-content" tabIndex={-1} className="mx-auto min-h-screen w-full max-w-site">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<SiteFooter />
|
||||
<TelemetryWrapper />
|
||||
|
||||
@@ -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 = () => <div className="col-span-12 h-px bg-border/30" />
|
||||
|
||||
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 (
|
||||
<main className="relative lg:-ml-10">
|
||||
<div className="mx-auto w-full min-w-0 flex flex-col gap-16">
|
||||
{/* Component Showcase with Grid */}
|
||||
<div className="relative z-10 h-full w-full overflow-y-auto">
|
||||
{/* Grid Container */}
|
||||
<div className="relative">
|
||||
{/* Grid Lines - Vertical (Columns) */}
|
||||
{Array.from({ length: 13 }).map((_, i) => (
|
||||
<div
|
||||
key={`col-line-${i}`}
|
||||
className="absolute top-0 bottom-0 w-px bg-border/30 z-10 first:hidden last:hidden"
|
||||
style={{
|
||||
left: `${(i / 12) * 100}%`,
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Grid Content */}
|
||||
<div className="grid grid-cols-12 gap-0 relative z-20 pb-32">
|
||||
{/* Heading Section */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-8 pb-8">
|
||||
<div className="flex flex-col gap-8 justify-start pt-16 md:pt-32">
|
||||
<div className="max-w-2xl">
|
||||
<h1 className="text-4xl text-foreground mb-3 font-medium tracking-tight">
|
||||
Blocks for Supabase Projects
|
||||
</h1>
|
||||
<h2 className="text-lg text-foreground-light mb-4">
|
||||
A collection of components, blocks, and developer tools that connect your
|
||||
project to Supabase via a single command.
|
||||
</h2>
|
||||
<div className="flex gap-3 mt-4">
|
||||
<Button_Shadcn_ variant="secondary" size="lg">
|
||||
<Link href="/docs/getting-started/quickstart">Get Started</Link>
|
||||
</Button_Shadcn_>
|
||||
<Button_Shadcn_ variant="outline" size="lg">
|
||||
<a href="/docs/guides/ai-tools/ai-skills">Install Skills</a>
|
||||
</Button_Shadcn_>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Password-based Authentication */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Password-based Authentication</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/password-based-auth"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4">
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Social Authentication */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Social Authentication</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/social-auth"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4">
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Realtime Cursors */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Realtime Cursors</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/realtime-cursor"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4 grid md:flex rounded-lg overflow-hidden">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Dropzone */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>File Upload</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/dropzone"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4 -mb-12">
|
||||
<ComponentPreview name="dropzone-demo" showCode={false} />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Current User Avatar */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Current User Avatar</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/current-user-avatar"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4 -mb-12">
|
||||
<ComponentPreview name="current-user-avatar-preview" showCode={false} />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Realtime Avatar Stack */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Realtime Avatar Stack</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/realtime-avatar-stack"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4 -mb-12">
|
||||
<ComponentPreview name="realtime-avatar-stack-preview" showCode={false} />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Realtime Chat */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Realtime Chat</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/realtime-chat"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4 grid md:flex rounded-lg overflow-hidden">
|
||||
<BlockPreview name={`realtime-chat-demo?roomName=${roomName}`} isPair />
|
||||
<BlockPreview name={`realtime-chat-demo?roomName=${roomName}`} isPair />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
|
||||
{/* Infinite Query Hook */}
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
|
||||
<span>Infinite Query Hook</span>
|
||||
<Link
|
||||
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
|
||||
href="/docs/nextjs/social-auth"
|
||||
>
|
||||
Go to block ➔
|
||||
</Link>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
|
||||
<div className="-mt-4">
|
||||
<BlockPreview name="infinite-list-demo" />
|
||||
</div>
|
||||
</div>
|
||||
<HorizontalGridLine />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Suspense>
|
||||
<LibraryOverview />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div>
|
||||
<div className="preview min-h-svh">
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="relative flex h-screen w-full items-center justify-center bg-studio p-6 preview md:p-10">
|
||||
<div className="relative flex h-screen w-full items-center justify-center p-6 preview md:p-10">
|
||||
<div className="pointer-events-none absolute z-0 h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.02)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
|
||||
<div className="z-10 w-full max-w-lg">{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,9 @@ export default function Page() {
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Thank you for signing up!</CardTitle>
|
||||
<CardTitle className="font-medium text-lg tracking-normal">
|
||||
Thank you for signing up!
|
||||
</CardTitle>
|
||||
<CardDescription>Check your email to confirm</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -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 (
|
||||
<html style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<head>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body, #root, main {
|
||||
height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
|
||||
<BaseInjector />
|
||||
<Providers>
|
||||
<div
|
||||
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
|
||||
style={{ minHeight: '100%' }}
|
||||
>
|
||||
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
|
||||
<div className="w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="preview relative flex min-h-svh w-full items-center justify-center overflow-hidden p-6 md:p-10">
|
||||
<div className="pointer-events-none absolute inset-0 z-0 bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
|
||||
<div className="relative w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
|
||||
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
|
||||
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
|
||||
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
|
||||
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
|
||||
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
|
||||
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ const SocialAuthDemo = () => {
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Welcome!</CardTitle>
|
||||
<CardTitle className="font-medium text-lg tracking-normal">Welcome!</CardTitle>
|
||||
<CardDescription>Sign in to your account to continue</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Metadata } from 'next'
|
||||
|
||||
import { BaseInjector } from './../base-injector'
|
||||
import { Providers } from '@/app/Providers'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Social Auth Example',
|
||||
@@ -9,34 +8,12 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<head>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
html, body, #root, main {
|
||||
height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
|
||||
<BaseInjector />
|
||||
<Providers>
|
||||
<div
|
||||
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
|
||||
style={{ minHeight: '100%' }}
|
||||
>
|
||||
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
|
||||
<div className="w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
<>
|
||||
<BaseInjector />
|
||||
<div className="preview relative flex min-h-svh w-full items-center justify-center overflow-hidden p-6 md:p-10">
|
||||
<div className="pointer-events-none absolute inset-0 z-0 bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
|
||||
<div className="relative w-full max-w-sm">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -4,20 +4,26 @@ import '@/styles/globals.css'
|
||||
|
||||
import { FeatureFlagProvider, TelemetryTagManager } from 'common'
|
||||
import { genFaviconData } from 'common/MetaFavicons/app-router'
|
||||
import { Inter } from 'next/font/google'
|
||||
import { Inter, Manrope, Source_Code_Pro } from 'next/font/google'
|
||||
|
||||
import { Providers } from './Providers'
|
||||
import { Toaster } from './toaster'
|
||||
import { API_URL } from '@/lib/constants'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' })
|
||||
const manrope = Manrope({ subsets: ['latin'], variable: '--font-manrope', display: 'swap' })
|
||||
const sourceCodePro = Source_Code_Pro({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-source-code-pro',
|
||||
display: 'swap',
|
||||
})
|
||||
|
||||
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
|
||||
|
||||
export const metadata: Metadata = {
|
||||
applicationName: 'Supabase Library',
|
||||
title: 'Supabase Library',
|
||||
description: 'Provides a library of components for your project',
|
||||
description: 'Supabase blocks and starter apps for authentication, storage, realtime, and more',
|
||||
metadataBase: new URL('https://supabase.com'),
|
||||
icons: genFaviconData(BASE_PATH),
|
||||
openGraph: {
|
||||
@@ -42,9 +48,11 @@ interface RootLayoutProps {
|
||||
|
||||
export default async function Layout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang="en" data-scroll-behavior="smooth" suppressHydrationWarning>
|
||||
<head />
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<body
|
||||
className={`${inter.variable} ${manrope.variable} ${sourceCodePro.variable} font-sans antialiased`}
|
||||
>
|
||||
<TelemetryTagManager />
|
||||
<FeatureFlagProvider API_URL={API_URL}>
|
||||
<Providers>
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import { File } from 'lucide-react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useState } from 'react'
|
||||
import { flattenTree, TreeView, TreeViewItem } from 'ui'
|
||||
import { CodeBlock, type CodeBlockLang } from 'ui-patterns/CodeBlock'
|
||||
import { cn, flattenTree, TreeView, TreeViewItem } from 'ui'
|
||||
import type { CodeBlockLang } from 'ui-patterns/CodeBlock'
|
||||
|
||||
import { RegistryNode } from '@/lib/process-registry'
|
||||
|
||||
const CodeBlock = dynamic(
|
||||
() => import('ui-patterns/CodeBlock').then((module) => module.CodeBlock),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="p-4 text-xs text-foreground-lighter">Loading code preview...</div>
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
interface BlockItemCodeProps {
|
||||
files: RegistryNode[]
|
||||
embedded?: boolean
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
@@ -63,13 +75,13 @@ const findFirstFile = (nodes: RegistryNode[]): RegistryNode | null => {
|
||||
return null
|
||||
}
|
||||
|
||||
export function BlockItemCode({ files }: BlockItemCodeProps) {
|
||||
export function BlockItemCode({ files, embedded = false }: BlockItemCodeProps) {
|
||||
// Find the first file to select by default
|
||||
const [selectedFile, setSelectedFile] = useState<RegistryNode | null>(findFirstFile(files))
|
||||
const flattenedData = flattenTree({ name: '', children: flattenChildren(files) })
|
||||
|
||||
// Handle file selection from the TreeView
|
||||
const handleNodeSelect = (element: any) => {
|
||||
const handleNodeSelect = (filePath: string) => {
|
||||
const findFileByPath = (nodes: RegistryNode[], path: string): RegistryNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.path === path) {
|
||||
@@ -85,7 +97,6 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const filePath = element.metadata.path
|
||||
const foundFile = findFileByPath(files, filePath)
|
||||
|
||||
if (foundFile?.type === 'directory') return
|
||||
@@ -94,9 +105,14 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex mt-4 border rounded-lg overflow-hidden h-[652px] not-prose">
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col sm:flex-row overflow-hidden not-prose',
|
||||
embedded ? 'h-full' : 'mt-4 border rounded-lg h-[652px]'
|
||||
)}
|
||||
>
|
||||
{/* File browser sidebar */}
|
||||
<div className="w-64 py-2 border-r bg-muted/30 overflow-y-auto">
|
||||
<div className="h-44 w-full shrink-0 overflow-auto border-b bg-muted/30 py-2 sm:h-full sm:w-64 sm:border-b-0 sm:border-r">
|
||||
<TreeView
|
||||
data={flattenedData}
|
||||
aria-label="file browser"
|
||||
@@ -105,7 +121,11 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
|
||||
defaultSelectedIds={flattenedData
|
||||
.filter((n) => n.metadata?.path === selectedFile?.path)
|
||||
.map((n) => n.id)}
|
||||
onNodeSelect={({ element }) => handleNodeSelect(element)}
|
||||
onNodeSelect={({ element }) => {
|
||||
if (typeof element.metadata?.path === 'string') {
|
||||
handleNodeSelect(element.metadata.path)
|
||||
}
|
||||
}}
|
||||
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
|
||||
<TreeViewItem
|
||||
{...getNodeProps()}
|
||||
@@ -124,7 +144,7 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
|
||||
{/* Code display area */}
|
||||
{selectedFile?.content ? (
|
||||
<CodeBlock
|
||||
wrapperClassName="w-full"
|
||||
wrapperClassName="w-full min-h-0 min-w-0 flex-1"
|
||||
className="h-full max-w-none w-full! flex-1 font-mono text-xs rounded-none border-none"
|
||||
language={languageFor(selectedFile.name)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
|
||||
|
||||
export function BlockOverviewTabs({
|
||||
children,
|
||||
files,
|
||||
}: {
|
||||
children?: ReactNode
|
||||
files?: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="library-block-overview relative z-0 mt-4">
|
||||
<Tabs
|
||||
defaultValue={children ? 'preview' : 'files'}
|
||||
className="overflow-hidden rounded-xl border bg-muted/50"
|
||||
>
|
||||
<TabsList aria-label="Block overview" className="gap-0 px-3">
|
||||
{children && (
|
||||
<TabsTrigger
|
||||
value="preview"
|
||||
className="px-3 py-3 text-xs data-[state=active]:border-brand"
|
||||
>
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{files && (
|
||||
<TabsTrigger
|
||||
value="files"
|
||||
className="px-3 py-3 text-xs data-[state=active]:border-brand"
|
||||
>
|
||||
Files
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
{children && (
|
||||
<TabsContent
|
||||
value="preview"
|
||||
forceMount
|
||||
className="library-overview-preview mt-0 data-[state=inactive]:hidden"
|
||||
>
|
||||
{children}
|
||||
</TabsContent>
|
||||
)}
|
||||
{files && (
|
||||
<TabsContent value="files" className="mt-0 h-[520px] md:h-[600px]">
|
||||
{files}
|
||||
</TabsContent>
|
||||
)}
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import path from 'node:path'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { BlockItemCode } from './block-item-code'
|
||||
import { BlockOverviewTabs } from './block-overview-tabs'
|
||||
import { generateRegistryTree } from '@/lib/process-registry'
|
||||
import { resolveRegistryItem } from '@/lib/registry-resolution'
|
||||
import { registry } from '@/registry'
|
||||
|
||||
export function BlockOverview({
|
||||
name,
|
||||
children,
|
||||
showFiles = false,
|
||||
}: {
|
||||
name: string
|
||||
children?: ReactNode
|
||||
showFiles?: boolean
|
||||
}) {
|
||||
const resolved = registry.items.some((item) => item.name === name)
|
||||
? resolveRegistryItem((itemName) => registry.items.find((item) => item.name === itemName), name)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<BlockOverviewTabs
|
||||
files={
|
||||
showFiles ? (
|
||||
<div className="flex h-full flex-col">
|
||||
{resolved && (
|
||||
// px-6 lines this up with the tab labels, which the list and trigger each inset by 12px.
|
||||
<p className="border-b px-6 py-3 text-xs text-foreground-light">
|
||||
Supabase files
|
||||
{resolved.firstPartyDependencies.length > 0
|
||||
? ', including registry dependencies'
|
||||
: ''}
|
||||
.
|
||||
{resolved.externalRegistryDependencies.length > 0 && (
|
||||
<>
|
||||
{' '}
|
||||
External UI dependencies: {resolved.externalRegistryDependencies.join(', ')}.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<div className="min-h-0 flex-1">
|
||||
<BlockItemCode
|
||||
files={generateRegistryTree(
|
||||
path.join(process.cwd(), 'public', 'r', `${name}.json`)
|
||||
)}
|
||||
embedded
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</BlockOverviewTabs>
|
||||
)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export function BlockPreview({ name, wide = false, isPair = false }: ComponentPr
|
||||
}
|
||||
>
|
||||
<iframe
|
||||
title={`${name.split('/')[0].replaceAll('-', ' ')} preview`}
|
||||
src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/example/${name}`}
|
||||
style={{
|
||||
border: 'none',
|
||||
@@ -36,10 +37,10 @@ export function BlockPreview({ name, wide = false, isPair = false }: ComponentPr
|
||||
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
|
||||
|
||||
return (
|
||||
<div className={cn('mt-4 w-full', wideClasses)}>
|
||||
<div className={cn('library-block-preview relative isolate mt-4 w-full', wideClasses)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative border rounded-lg overflow-hidden bg-muted min-h-[150px] h-[600px]',
|
||||
'relative border overflow-hidden min-h-[150px] h-[600px]',
|
||||
isPair && 'rounded-none'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
import {
|
||||
Bot,
|
||||
Check,
|
||||
File,
|
||||
Github,
|
||||
LockKeyhole,
|
||||
MoreHorizontal,
|
||||
MousePointer2,
|
||||
Search,
|
||||
Send,
|
||||
Upload,
|
||||
UserRound,
|
||||
Workflow,
|
||||
} from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import type { CatalogPreviewKind } from '@/config/library'
|
||||
|
||||
const previewTitles: Partial<Record<CatalogPreviewKind, string>> = {
|
||||
table: 'customers',
|
||||
storage: 'product-assets',
|
||||
chat: 'team chat',
|
||||
editor: 'index.ts',
|
||||
mcp: 'mcp-server',
|
||||
agents: 'connected agents',
|
||||
dashboard: 'acme workspace',
|
||||
client: 'supabase.ts',
|
||||
flow: 'shared canvas',
|
||||
}
|
||||
|
||||
function PreviewWindow({ title, children }: { title: string; children: ReactNode }) {
|
||||
return (
|
||||
<div className="w-full max-w-xs overflow-hidden rounded-lg border bg-background shadow-lg">
|
||||
<div className="flex h-8 items-center justify-between border-b px-3 text-foreground-light">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-brand" />
|
||||
<span className="text-[10px]">{title}</span>
|
||||
</div>
|
||||
<MoreHorizontal className="size-3.5" />
|
||||
</div>
|
||||
<div className="flex min-h-28 flex-col justify-center gap-2.5 p-3">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CatalogPreview({ kind }: { kind: CatalogPreviewKind }) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="flex h-full w-full select-none flex-col p-5 text-[10px] text-foreground [mask-image:linear-gradient(to_bottom,black_calc(100%-20px),transparent)] md:p-6 md:[mask-image:linear-gradient(to_bottom,black_calc(100%-24px),transparent)]"
|
||||
>
|
||||
<div className="my-auto flex w-full shrink-0 items-center justify-center">
|
||||
{kind === 'auth' || kind === 'social' || kind === 'consent' ? (
|
||||
<div className="w-full max-w-[200px] rounded-lg border bg-background p-4 shadow-lg">
|
||||
<div className="mb-2 flex size-6 items-center justify-center rounded-md border bg-surface-100">
|
||||
<LockKeyhole className="size-3 text-primary" />
|
||||
</div>
|
||||
<p className="text-xs font-medium">
|
||||
{kind === 'consent' ? 'Connect to Acme' : 'Welcome back'}
|
||||
</p>
|
||||
<p className="mt-1 text-[9px] text-foreground-light">
|
||||
{kind === 'consent'
|
||||
? 'Review the access requested below.'
|
||||
: 'Sign in to continue to your account.'}
|
||||
</p>
|
||||
<div className="mt-3 flex flex-col gap-1.5">
|
||||
{kind === 'social' ? (
|
||||
<>
|
||||
<div className="flex items-center justify-center gap-2 rounded border px-2 py-1.5">
|
||||
<Github className="size-3" />
|
||||
Continue with GitHub
|
||||
</div>
|
||||
<div className="rounded border px-2 py-1.5 text-center">Continue with Google</div>
|
||||
</>
|
||||
) : kind === 'consent' ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 py-1">
|
||||
<Check className="size-3 text-primary" />
|
||||
Read your profile
|
||||
</div>
|
||||
<div className="flex items-center gap-2 py-1">
|
||||
<Check className="size-3 text-primary" />
|
||||
Access your projects
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded border px-2 py-1.5 text-foreground-lighter">
|
||||
you@example.com
|
||||
</div>
|
||||
<div className="rounded border px-2 py-1.5 text-foreground-lighter">••••••••</div>
|
||||
</>
|
||||
)}
|
||||
{kind !== 'social' && (
|
||||
<div className="rounded border border-brand/30 bg-brand/20 py-1.5 text-center text-foreground">
|
||||
{kind === 'consent' ? 'Allow access' : 'Sign in'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : kind === 'avatar' || kind === 'avatars' ? (
|
||||
<div className="flex w-full max-w-[210px] flex-col items-center rounded-lg border bg-background px-4 py-5 shadow-lg">
|
||||
<div className="flex -space-x-2">
|
||||
{(kind === 'avatar' ? ['JD'] : ['JD', 'AS', 'MK', '+2']).map((initials, index) => (
|
||||
<div
|
||||
key={initials}
|
||||
className={`relative flex size-10 items-center justify-center rounded-full border-2 border-background text-xs ${index % 2 === 0 ? 'bg-brand/20 text-primary' : 'bg-surface-300 text-foreground-light'}`}
|
||||
>
|
||||
{initials}
|
||||
{index === 0 && (
|
||||
<span className="absolute bottom-0 right-0 size-2.5 rounded-full border-2 border-background bg-brand" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-3 text-xs font-medium">
|
||||
{kind === 'avatar' ? 'Jamie Davis' : 'Better together'}
|
||||
</p>
|
||||
<p className="mt-1 text-foreground-light">
|
||||
{kind === 'avatar' ? 'jamie@example.com' : '5 people in this workspace'}
|
||||
</p>
|
||||
<div className="mt-4 w-full rounded border px-3 py-1.5 text-center text-foreground-light">
|
||||
{kind === 'avatar' ? 'Account settings' : 'Everyone is up to date'}
|
||||
</div>
|
||||
</div>
|
||||
) : kind === 'cursors' ? (
|
||||
<div className="relative h-36 w-full max-w-xs rounded-lg border bg-background shadow-lg">
|
||||
<div className="absolute inset-x-5 top-5 h-2 w-2/5 rounded bg-surface-200" />
|
||||
<div className="absolute inset-x-5 top-10 h-2 rounded bg-surface-200" />
|
||||
<div className="absolute inset-x-5 top-14 h-2 w-3/5 rounded bg-surface-200" />
|
||||
<div className="absolute left-10 top-16 text-primary">
|
||||
<MousePointer2 className="size-5 fill-current" />
|
||||
<span className="ml-4 rounded bg-brand/20 px-2 py-1">Jamie</span>
|
||||
</div>
|
||||
<div className="absolute bottom-7 right-6 text-foreground-light">
|
||||
<MousePointer2 className="size-5 fill-current" />
|
||||
<span className="ml-4 rounded bg-surface-300 px-2 py-1">Alex</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<PreviewWindow title={previewTitles[kind] ?? 'workspace'}>
|
||||
{kind === 'table' && (
|
||||
<>
|
||||
<div className="flex items-center gap-2 rounded border bg-surface-100 px-2 py-1 text-foreground-light">
|
||||
<Search className="size-3" />
|
||||
Filter rows
|
||||
</div>
|
||||
{['jamie', 'alex', 'morgan'].map((name, index) => (
|
||||
<div key={name} className="flex items-center gap-3 border-b pb-1.5">
|
||||
<span className="text-foreground-lighter">0{index + 1}</span>
|
||||
<span className="truncate">{name}@example.com</span>
|
||||
<span className="ml-auto size-1.5 shrink-0 rounded-full bg-brand" />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{kind === 'storage' && (
|
||||
<>
|
||||
<div className="flex items-center justify-center gap-2 rounded-md border border-dashed py-3 text-foreground-light">
|
||||
<Upload className="size-3.5" />
|
||||
Drop files to upload
|
||||
</div>
|
||||
{['cover.png', 'design.fig'].map((name) => (
|
||||
<div key={name} className="flex items-center gap-2 rounded border px-2 py-1.5">
|
||||
<File className="size-3 text-foreground-light" />
|
||||
<span>{name}</span>
|
||||
<Check className="ml-auto size-3 text-primary" />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{kind === 'chat' && (
|
||||
<>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-surface-200">
|
||||
JD
|
||||
</span>
|
||||
<div className="rounded-md border bg-surface-100 px-2 py-1.5">
|
||||
How is the new project going?
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-8 rounded-md border border-brand/20 bg-brand/10 px-2 py-1.5">
|
||||
Ready to share a first look.
|
||||
</div>
|
||||
<div className="mt-1 flex items-center justify-between rounded border px-2 py-1.5 text-foreground-lighter">
|
||||
<span>Write a message...</span>
|
||||
<Send className="size-3" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{(kind === 'editor' || kind === 'client') && (
|
||||
<div className="overflow-hidden whitespace-nowrap font-mono text-[9px] leading-5 text-foreground-light">
|
||||
<p>
|
||||
<span className="text-primary">import</span> {'{ createClient }'}
|
||||
</p>
|
||||
<p className="pl-3">from '@supabase/supabase-js'</p>
|
||||
<p className="mt-2">
|
||||
<span className="text-primary">const</span> supabase = createClient(
|
||||
</p>
|
||||
<p className="pl-3">SUPABASE_URL,</p>
|
||||
<p className="pl-3">SUPABASE_PUBLISHABLE_KEY</p>
|
||||
<p>)</p>
|
||||
</div>
|
||||
)}
|
||||
{kind === 'dashboard' && (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{['Users', 'Queries', 'Files'].map((label, index) => (
|
||||
<div className="rounded border p-2" key={label}>
|
||||
<p className="text-[9px] text-foreground-light">{label}</p>
|
||||
<p className="mt-1 text-xs">{['824', '128', '42'][index]}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex h-12 items-end gap-1.5 pt-2">
|
||||
{['h-2/5', 'h-3/5', 'h-1/2', 'h-4/5', 'h-3/5', 'h-full', 'h-4/5', 'h-full'].map(
|
||||
(height, index) => (
|
||||
<div key={index} className={`flex-1 rounded-t bg-brand/30 ${height}`} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{kind === 'mcp' &&
|
||||
['get_user', 'list_projects', 'search_documents'].map((tool, index) => (
|
||||
<div key={tool} className="flex items-center gap-2 rounded border p-2">
|
||||
<Bot
|
||||
className={`size-3.5 ${index === 0 ? 'text-primary' : 'text-foreground-light'}`}
|
||||
/>
|
||||
<span>{tool}</span>
|
||||
<span className="ml-auto text-foreground-lighter">tool</span>
|
||||
</div>
|
||||
))}
|
||||
{kind === 'agents' && (
|
||||
<>
|
||||
{['Claude Code', 'Codex'].map((client, index) => (
|
||||
<div key={client} className="flex items-center gap-2 rounded border p-2">
|
||||
<Bot
|
||||
className={`size-3.5 ${index === 0 ? 'text-primary' : 'text-foreground-light'}`}
|
||||
/>
|
||||
<span>{client}</span>
|
||||
<span className="ml-auto text-foreground-lighter">Revoke</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2 rounded-md border border-dashed px-2 py-1.5 text-foreground-light">
|
||||
<LockKeyhole className="size-3" />
|
||||
Connect an agent
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{kind === 'flow' && (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center gap-2 rounded border px-3 py-2">
|
||||
<Workflow className="size-3 text-primary" />
|
||||
New event
|
||||
</div>
|
||||
<div className="h-4 w-px bg-border" />
|
||||
<div className="h-px w-2/3 bg-border" />
|
||||
<div className="flex w-full justify-around">
|
||||
<div className="h-3 w-px bg-border" />
|
||||
<div className="h-3 w-px bg-border" />
|
||||
</div>
|
||||
<div className="flex w-full justify-around gap-2">
|
||||
<div className="rounded border px-2 py-2">Update record</div>
|
||||
<div className="flex items-center gap-1 rounded border px-2 py-2">
|
||||
<UserRound className="size-3" />
|
||||
Notify team
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PreviewWindow>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
@@ -13,14 +13,19 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
DialogDescription,
|
||||
DialogProps,
|
||||
DialogTitle,
|
||||
} from 'ui'
|
||||
|
||||
import { COMMAND_ITEMS } from '@/config/docs'
|
||||
import { gettingStarted } from '@/config/docs'
|
||||
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function CommandMenu({ ...props }: DialogProps) {
|
||||
const router = useRouter()
|
||||
const { framework } = useFramework()
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
@@ -55,37 +60,59 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
`relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12
|
||||
`@container relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12 md:pr-2.5 lg:pr-12
|
||||
hover:border-foreground-muted hover:bg-surface-100 hover:text-foreground-lighter
|
||||
`
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
{...props}
|
||||
>
|
||||
<span className="hidden lg:inline-flex">Search Library...</span>
|
||||
<span className="inline-flex lg:hidden">Search...</span>
|
||||
<kbd className="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded-sm border bg-surface-200 px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex text-foreground-light">
|
||||
<span className="hidden @min-[7.5rem]:inline-flex">Search library...</span>
|
||||
<span className="inline-flex @min-[7.5rem]:hidden">Search...</span>
|
||||
<kbd className="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded-sm border bg-surface-200 px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex md:hidden lg:flex text-foreground-light">
|
||||
<span className="text-sm">⌘</span>K
|
||||
</kbd>
|
||||
</Button>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTitle className="sr-only">Search the library</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Find guides, blocks, starter apps, and theme settings.
|
||||
</DialogDescription>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandList className="min-h-0 max-h-[min(400px,calc(100dvh-8rem))] overscroll-contain [&_[cmdk-group]]:p-2! [&_[cmdk-item]]:rounded-md [&_[cmdk-item]]:p-2!">
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup key="pages" heading="Pages">
|
||||
{COMMAND_ITEMS.map((navItem) => (
|
||||
{gettingStarted.items.map((navItem) => (
|
||||
<CommandItem
|
||||
key={navItem.href}
|
||||
value={navItem.label}
|
||||
value={navItem.title}
|
||||
onSelect={() => runCommand(() => router.push(navItem.href as string))}
|
||||
>
|
||||
<div className="mr-2 flex h-4 w-4 items-center justify-center">
|
||||
<CircleIcon className="h-3 w-3" strokeWidth={1} />
|
||||
</div>
|
||||
{navItem.label}
|
||||
{navItem.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
{libraryCategories.map((category) => (
|
||||
<CommandGroup key={category.slug} heading={category.name}>
|
||||
{libraryBlocks
|
||||
.filter((block) => block.category === category.name)
|
||||
.map((block) => (
|
||||
<CommandItem
|
||||
key={block.slug}
|
||||
value={`${block.title} ${block.category} ${block.tags.join(' ')}`}
|
||||
onSelect={() =>
|
||||
runCommand(() => {
|
||||
const href = getLibraryBlockHref(block, framework)
|
||||
if (block.external) window.location.assign(href)
|
||||
else router.push(href)
|
||||
})
|
||||
}
|
||||
>
|
||||
{block.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Theme">
|
||||
<CommandItem onSelect={() => runCommand(() => setTheme('light'))}>
|
||||
|
||||
@@ -5,57 +5,25 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
|
||||
|
||||
import { CommandCopyButton } from './command-copy-button'
|
||||
import { useLocalStorage } from './use-local-storage'
|
||||
import {
|
||||
getInstallCommands,
|
||||
type PackageManager,
|
||||
type ShadcnFramework,
|
||||
} from '@/lib/install-command'
|
||||
|
||||
interface CommandCopyProps {
|
||||
name: string
|
||||
highlight?: boolean
|
||||
// For Vue, we need to use the `shadcn-vue` package instead of `shadcn`
|
||||
framework?: 'react' | 'vue'
|
||||
framework?: ShadcnFramework
|
||||
}
|
||||
|
||||
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'package-manager-copy-command'
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'production') {
|
||||
// we have a special alias for the production environment, added in https://github.com/shadcn-ui/ui/pull/8161
|
||||
return `@supabase`
|
||||
} else if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'preview') {
|
||||
return `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
|
||||
} else {
|
||||
return 'http://localhost:3004'
|
||||
}
|
||||
}
|
||||
|
||||
const getComponentPath = (name: string) => {
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'production') {
|
||||
return `/${name}`
|
||||
} else {
|
||||
return `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/r/${name}.json`
|
||||
}
|
||||
}
|
||||
|
||||
export function Command({ name, highlight, framework = 'react' }: CommandCopyProps) {
|
||||
const [value, setValue] = useLocalStorage(LOCAL_STORAGE_KEY, 'npm')
|
||||
|
||||
const baseUrl = getBaseUrl()
|
||||
const componentPath = getComponentPath(name)
|
||||
|
||||
const commands: Record<PackageManager, string> =
|
||||
framework === 'vue'
|
||||
? {
|
||||
npm: `npx shadcn-vue@latest add ${baseUrl}${componentPath}`,
|
||||
pnpm: `pnpm dlx shadcn-vue@latest add ${baseUrl}${componentPath}`,
|
||||
yarn: `yarn dlx shadcn-vue@latest add ${baseUrl}${componentPath}`,
|
||||
bun: `bunx --bun shadcn-vue@latest add ${baseUrl}${componentPath}`,
|
||||
}
|
||||
: {
|
||||
npm: `npx shadcn@latest add ${baseUrl}${componentPath}`,
|
||||
pnpm: `pnpm dlx shadcn@latest add ${baseUrl}${componentPath}`,
|
||||
yarn: `yarn dlx shadcn@latest add ${baseUrl}${componentPath}`,
|
||||
bun: `bunx --bun shadcn@latest add ${baseUrl}${componentPath}`,
|
||||
}
|
||||
const commands = getInstallCommands(name, { framework })
|
||||
|
||||
return (
|
||||
<Tabs value={value} onValueChange={setValue} className="w-full">
|
||||
|
||||
@@ -51,7 +51,7 @@ export function ComponentPreview({
|
||||
}
|
||||
|
||||
return <Component />
|
||||
}, [name, config.style])
|
||||
}, [name])
|
||||
|
||||
const Codes = React.Children.toArray(children) as React.ReactElement[]
|
||||
const Code = Codes[index]
|
||||
@@ -81,13 +81,8 @@ export function ComponentPreview({
|
||||
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
|
||||
|
||||
return (
|
||||
<div className={cn('mt-4 mb-12', wideClasses)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative bg-studio',
|
||||
showCode ? 'rounded-tl-md rounded-tr-md border-t border-l border-r' : 'rounded-md border'
|
||||
)}
|
||||
>
|
||||
<div className={cn('library-component-preview mt-4 mb-12', wideClasses)}>
|
||||
<div className={cn('relative', showCode ? 'border-t border-l border-r' : 'border')}>
|
||||
{showGrid && (
|
||||
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,oklch(from_var(--foreground-default)_l_c_h_/_0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-size-[24px_24px]"></div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from 'ui'
|
||||
|
||||
interface CopyDocPromptProps {
|
||||
title: string
|
||||
markdownPath: string
|
||||
intent: 'create-app' | 'add-to-project'
|
||||
}
|
||||
|
||||
export function CopyDocPrompt({ title, markdownPath, intent }: CopyDocPromptProps) {
|
||||
const [status, setStatus] = useState<'idle' | 'copied' | 'error'>('idle')
|
||||
|
||||
useEffect(() => {
|
||||
if (status !== 'copied') return
|
||||
const timeout = window.setTimeout(() => setStatus('idle'), 2500)
|
||||
return () => window.clearTimeout(timeout)
|
||||
}, [status])
|
||||
|
||||
async function copyPrompt() {
|
||||
const url = new URL(markdownPath, window.location.origin).href
|
||||
const task =
|
||||
intent === 'create-app'
|
||||
? `Help me create a new application using ${title} from Supabase Library. Inspect my workspace and create the app in a suitable directory.`
|
||||
: `Help me add ${title} from Supabase Library to my existing project. Inspect my project structure and reuse any Supabase client setup that is already in place.`
|
||||
const prompt = [
|
||||
task,
|
||||
`Read ${url} for the complete guide before making changes.`,
|
||||
"To add the block's files, prefer the guide's shadcn install command over copying them from the registry JSON; the CLI also installs npm dependencies and related blocks.",
|
||||
'Follow the rest of the guide, adapting it to my project, and verify that the setup works.',
|
||||
].join(' ')
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(prompt)
|
||||
setStatus('copied')
|
||||
} catch {
|
||||
setStatus('error')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex max-w-full flex-col items-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
// Button's medium icon container forces 20px, overriding the icons' own size.
|
||||
className="rounded-full border-0 px-4 [&_svg]:size-4!"
|
||||
icon={
|
||||
status === 'copied' ? (
|
||||
<Check size={16} strokeWidth={2} />
|
||||
) : (
|
||||
<Copy size={16} strokeWidth={2} />
|
||||
)
|
||||
}
|
||||
onClick={copyPrompt}
|
||||
>
|
||||
{status === 'copied' ? 'Prompt copied' : 'Copy agent prompt'}
|
||||
</Button>
|
||||
<span
|
||||
role="status"
|
||||
className={status === 'error' ? 'max-w-sm text-xs text-foreground-light' : 'sr-only'}
|
||||
>
|
||||
{status === 'error'
|
||||
? 'Unable to copy the prompt. Try again.'
|
||||
: status === 'copied'
|
||||
? 'Prompt copied to clipboard'
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, type ReactNode } from 'react'
|
||||
import { cn, Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
|
||||
|
||||
import { frameworkTitles } from '@/config/docs'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
|
||||
export function FrameworkQuickstart({ children }: { children: ReactNode }) {
|
||||
const { framework, setFramework } = useFramework()
|
||||
const tabStripRef = useRef<HTMLDivElement>(null)
|
||||
const activeTabRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const strip = tabStripRef.current
|
||||
const tab = activeTabRef.current
|
||||
if (!strip || !tab) return
|
||||
|
||||
const stripBounds = strip.getBoundingClientRect()
|
||||
const tabBounds = tab.getBoundingClientRect()
|
||||
if (tabBounds.left < stripBounds.left) {
|
||||
strip.scrollLeft += tabBounds.left - stripBounds.left
|
||||
} else if (tabBounds.right > stripBounds.right) {
|
||||
strip.scrollLeft += tabBounds.right - stripBounds.right
|
||||
}
|
||||
}, [framework])
|
||||
|
||||
return (
|
||||
<Tabs value={framework} onValueChange={setFramework} className="w-full">
|
||||
<div ref={tabStripRef} className="overflow-x-auto">
|
||||
<TabsList aria-label="Framework setup guides" className="w-max min-w-full gap-0">
|
||||
{Object.entries(frameworkTitles).map(([value, label]) => (
|
||||
<TabsTrigger
|
||||
key={value}
|
||||
ref={value === framework ? activeTabRef : undefined}
|
||||
value={value}
|
||||
className="shrink-0 px-3 py-3 text-xs font-medium data-[state=active]:border-brand"
|
||||
>
|
||||
{label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</div>
|
||||
{children}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export function FrameworkQuickstartTab({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<TabsContent value={value} className="mt-8 [&>h2]:sr-only">
|
||||
{children}
|
||||
</TabsContent>
|
||||
)
|
||||
}
|
||||
|
||||
export function QuickstartStep({ number, children }: { number: string; children: ReactNode }) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'grid grid-cols-[1.75rem_minmax(0,1fr)] gap-4 border-t py-6',
|
||||
number === '1' && 'border-t-0 pt-0'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="flex size-7 items-center justify-center rounded-full border font-mono text-xs text-foreground-lighter"
|
||||
>
|
||||
{number}
|
||||
</span>
|
||||
<div className="min-w-0 [&_h3]:m-0 [&_h3]:font-sans [&_h3]:text-sm [&_h3]:font-semibold [&_p]:mt-2! [&_p]:text-sm [&_p]:leading-6 [&_pre]:mt-4 [&_pre]:mb-0 [&_pre]:rounded-md [&_pre_code]:text-xs">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -63,8 +63,11 @@ export function FrameworkSelector() {
|
||||
|
||||
return (
|
||||
<Select value={selectedFramework} onValueChange={onSelect}>
|
||||
<SelectTrigger className="w-[180px] mt-4 lg:mt-0">
|
||||
<SelectValue />
|
||||
<SelectTrigger
|
||||
aria-label="Framework"
|
||||
className="h-7 w-auto min-w-28 gap-2 rounded-md bg-surface-75 px-2 text-xs"
|
||||
>
|
||||
<SelectValue>{frameworkTitles[selectedFramework]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
'use client'
|
||||
|
||||
import { ArrowUpRight, Box } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { CatalogPreview } from '@/components/catalog-preview'
|
||||
import {
|
||||
getLibraryBlockHref,
|
||||
libraryBlocks,
|
||||
libraryCategories,
|
||||
type LibraryBlock,
|
||||
} from '@/config/library'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
|
||||
function LibraryCard({ block, framework }: { block: LibraryBlock; framework: string }) {
|
||||
return (
|
||||
<Link
|
||||
href={getLibraryBlockHref(block, framework)}
|
||||
className="group flex w-full min-w-0 flex-col gap-4 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand sm:w-[calc((100%-1.5rem)/2)] lg:w-[calc((100%-3rem)/3)] xl:w-[calc((100%-4.5rem)/4)]"
|
||||
>
|
||||
<div className="aspect-[4/3] overflow-hidden rounded-md border bg-surface-75 bg-[radial-gradient(var(--color-border)_1px,transparent_1px)] [background-size:20px_20px] transition-colors group-hover:border-stronger group-focus-visible:border-stronger">
|
||||
<div className="h-full w-full grayscale transition-[filter] duration-200 group-hover:grayscale-0 group-focus-visible:grayscale-0 motion-reduce:transition-none">
|
||||
<CatalogPreview kind={block.preview} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 px-1 pb-2">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h3 className="min-w-0 text-sm font-medium tracking-tight">{block.title}</h3>
|
||||
<span className="flex shrink-0 items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] leading-3 text-foreground-light">
|
||||
<Box aria-hidden="true" className="size-2.5" />
|
||||
{block.category === 'Starter apps' ? 'Starter' : 'Block'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="max-w-sm text-sm leading-5 text-foreground-lighter">{block.description}</p>
|
||||
{block.external && (
|
||||
<span className="flex items-center gap-1 text-xs text-foreground-lighter">
|
||||
View starter on GitHub
|
||||
<ArrowUpRight aria-hidden="true" className="size-3" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export function LibraryOverview() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const selectedCategory = searchParams.get('category')
|
||||
const activeCategory =
|
||||
libraryCategories.find((category) => category.name === selectedCategory)?.name ?? 'All'
|
||||
const { framework } = useFramework()
|
||||
|
||||
const filtered = libraryBlocks.filter(
|
||||
(block) => activeCategory === 'All' || block.category === activeCategory
|
||||
)
|
||||
const sections = libraryCategories
|
||||
.map((category) => ({
|
||||
...category,
|
||||
items: filtered.filter((block) => block.category === category.name),
|
||||
}))
|
||||
.filter((section) => section.items.length > 0)
|
||||
|
||||
function selectCategory(category: string) {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
if (category === 'All') params.delete('category')
|
||||
else params.set('category', category)
|
||||
router.replace(params.size ? `/?${params}` : '/', { scroll: false })
|
||||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section className="mx-auto flex max-w-7xl flex-col gap-6 px-4 pb-10 pt-16 md:px-8 md:pb-12 md:pt-24">
|
||||
<h1 className="max-w-2xl text-balance font-heading text-4xl font-medium tracking-normal sm:text-5xl sm:leading-none">
|
||||
Building blocks for your next backend.
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="mx-auto flex max-w-7xl flex-col gap-12 px-4 pb-16 md:px-8 md:pb-24"
|
||||
aria-labelledby="catalog-heading"
|
||||
>
|
||||
<h2 id="catalog-heading" className="sr-only">
|
||||
Browse blocks
|
||||
</h2>
|
||||
<div className="flex flex-col gap-3 border-b pb-12">
|
||||
<span id="category-label" className="text-xs font-medium text-foreground-lighter">
|
||||
Category
|
||||
</span>
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="category-label"
|
||||
className="grid w-fit max-w-full grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3"
|
||||
>
|
||||
{['All', ...libraryCategories.map((category) => category.name)].map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
variant="text"
|
||||
aria-pressed={activeCategory === category}
|
||||
onClick={() => selectCategory(category)}
|
||||
className={cn(
|
||||
'h-auto min-w-0 justify-start rounded-sm border-0 p-0 text-left text-sm leading-6 hover:bg-transparent hover:text-foreground',
|
||||
activeCategory === category ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{category}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="sr-only" role="status">
|
||||
{filtered.length} blocks found
|
||||
</p>
|
||||
{sections.length > 0 ? (
|
||||
<div className="flex flex-col gap-12">
|
||||
{sections.map((section) => (
|
||||
<section
|
||||
key={section.slug}
|
||||
id={section.slug}
|
||||
aria-labelledby={`${section.slug}-heading`}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<div className="flex flex-col gap-0">
|
||||
<h2 id={`${section.slug}-heading`} className="text-lg text-foreground">
|
||||
{section.name}
|
||||
</h2>
|
||||
<p className="text-lg text-foreground-muted">{section.description}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-6">
|
||||
{section.items.map((block) => (
|
||||
<LibraryCard key={block.slug} block={block} framework={framework} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-72 flex-col items-center justify-center gap-2 text-center">
|
||||
<p className="text-sm font-medium">No blocks found</p>
|
||||
<p className="text-lg text-foreground-muted">Try another category.</p>
|
||||
<Button
|
||||
variant="default"
|
||||
className="mt-3"
|
||||
onClick={() => {
|
||||
selectCategory('All')
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col justify-between gap-5 border-t pt-8 sm:flex-row sm:items-center">
|
||||
<p className="max-w-xl text-lg text-foreground-muted">
|
||||
Components, blocks, and starter apps for Supabase. Add blocks to your project with a
|
||||
single command.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button asChild variant="default">
|
||||
<Link href="/docs/getting-started/quickstart">Get started</Link>
|
||||
</Button>
|
||||
<Button asChild variant="text" iconRight={<ArrowUpRight className="size-3.5" />}>
|
||||
<a href="https://supabase.com/docs/guides/ai-tools/ai-skills">Install skills</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -2,14 +2,16 @@ import Link from 'next/link'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, cn } from 'ui'
|
||||
|
||||
import { BlockItem } from './block-item'
|
||||
import { BlockOverview } from './block-overview'
|
||||
import { BlockPreview } from './block-preview'
|
||||
import { Callout } from './callout'
|
||||
import { CatalogPreview } from './catalog-preview'
|
||||
import { ComponentPreview } from './component-preview'
|
||||
import { CopyButton } from './copy-button'
|
||||
import { DualRealtimeChat } from './dual-realtime-chat'
|
||||
import { DualRealtimeFlow } from './dual-realtime-flow'
|
||||
import { DualRealtimeMonaco } from './dual-realtime-monaco'
|
||||
import { RegistryBlock } from './registry-block'
|
||||
import { FrameworkQuickstart, FrameworkQuickstartTab, QuickstartStep } from './framework-quickstart'
|
||||
import { StyleWrapper } from './style-wrapper'
|
||||
import TanStackBeta from './tanstack-beta'
|
||||
import { TanstackDBGenerator } from './tanstack-db-generator'
|
||||
@@ -17,7 +19,11 @@ import { useMDXComponent } from '@/lib/use-mdx-component'
|
||||
import type { Style } from '@/registry/styles'
|
||||
|
||||
const components = {
|
||||
RegistryBlock,
|
||||
BlockOverview,
|
||||
CatalogPreview,
|
||||
FrameworkQuickstart,
|
||||
FrameworkQuickstartTab,
|
||||
QuickstartStep,
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
'use server'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import { BlockItemCode } from './block-item-code'
|
||||
import { generateRegistryTree } from '@/lib/process-registry'
|
||||
|
||||
export async function RegistryBlock({ itemName }: { itemName: string }) {
|
||||
const registryPath = path.join(process.cwd(), 'public', 'r', `${itemName}.json`)
|
||||
const tree = generateRegistryTree(registryPath)
|
||||
|
||||
return <BlockItemCode files={tree} />
|
||||
}
|
||||
@@ -19,7 +19,7 @@ interface NavigationItemProps
|
||||
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
||||
}
|
||||
|
||||
const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props }) => {
|
||||
export const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props }) => {
|
||||
const { setOpen } = useMobileMenu()
|
||||
const { framework } = useFramework()
|
||||
const pathname = usePathname()
|
||||
@@ -35,7 +35,9 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
|
||||
// An item supports the current framework if either:
|
||||
// 1. It has no framework restrictions (supports all frameworks)
|
||||
// 2. The current framework is in its list of supported frameworks
|
||||
return !hasFrameworkRestrictions || supportedFrameworks.includes(framework as any)
|
||||
return (
|
||||
!hasFrameworkRestrictions || supportedFrameworks.some((supported) => supported === framework)
|
||||
)
|
||||
}
|
||||
|
||||
// Build URL with priority:
|
||||
@@ -85,13 +87,14 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
|
||||
href={href || '#'}
|
||||
{...props}
|
||||
onClick={handleClick}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
className={cn(
|
||||
'relative',
|
||||
'flex',
|
||||
'items-center justify-between',
|
||||
'h-6',
|
||||
'min-h-8 rounded-md py-1.5',
|
||||
'text-sm',
|
||||
'px-6',
|
||||
'px-3',
|
||||
'transition-all',
|
||||
isActive
|
||||
? 'bg-selection text-foreground'
|
||||
@@ -99,14 +102,6 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{/* Active indicator bar */}
|
||||
<div
|
||||
className={cn(
|
||||
'transition',
|
||||
'absolute left-0 w-1 h-full bg-foreground',
|
||||
isActive ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
{item.title}
|
||||
{item.new && <Badge variant="success">New</Badge>}
|
||||
</Link>
|
||||
@@ -114,5 +109,3 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
|
||||
}
|
||||
|
||||
NavigationItem.displayName = 'NavigationItem'
|
||||
|
||||
export default NavigationItem
|
||||
@@ -1,134 +1,49 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
import { CommandMenu } from './command-menu'
|
||||
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
|
||||
import NavigationItem from '@/components/side-navigation-item'
|
||||
import {
|
||||
componentPages,
|
||||
gettingStarted,
|
||||
mcpBlocks,
|
||||
oauthBlocks,
|
||||
platformBlocks,
|
||||
starterApps,
|
||||
} from '@/config/docs'
|
||||
import { NavigationItem } from '@/components/side-navigation-item'
|
||||
import { gettingStarted } from '@/config/docs'
|
||||
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
import { useMobileMenu } from '@/hooks/use-mobile-menu'
|
||||
|
||||
export function SideNavigation() {
|
||||
const { framework } = useFramework()
|
||||
const { setOpen } = useMobileMenu()
|
||||
|
||||
function SideNavigation() {
|
||||
return (
|
||||
<nav className="flex flex-col h-full min-w-[220px]">
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<Link href="/">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="109"
|
||||
height="113"
|
||||
viewBox="0 0 109 113"
|
||||
fill="none"
|
||||
className="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z"
|
||||
fill="url(#paint0_linear)"
|
||||
<nav aria-label="Library navigation" className="flex min-w-0 flex-col p-5">
|
||||
<Link href="/" onClick={() => setOpen(false)} className="mb-8 pr-6 text-sm font-medium">
|
||||
Supabase Library
|
||||
</Link>
|
||||
<div className="mb-6 space-y-0.5">
|
||||
<p className="mb-2 px-3 text-xs text-foreground-lighter">Getting started</p>
|
||||
<NavigationItem item={{ title: 'Browse All Blocks', href: '/', items: [] }} />
|
||||
{gettingStarted.items.map((item) => (
|
||||
<NavigationItem item={item} key={item.href} />
|
||||
))}
|
||||
</div>
|
||||
{libraryCategories.map((category) => {
|
||||
const blocks = libraryBlocks.filter((block) => block.category === category.name)
|
||||
if (blocks.length === 0) return null
|
||||
|
||||
return (
|
||||
<div key={category.name} className="mb-6 space-y-0.5">
|
||||
<p className="mb-2 px-3 text-xs text-foreground-lighter">{category.name}</p>
|
||||
{blocks.map((block) => (
|
||||
<NavigationItem
|
||||
key={block.slug}
|
||||
item={{
|
||||
title: block.title,
|
||||
href: getLibraryBlockHref(block, framework),
|
||||
items: [],
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z"
|
||||
fill="url(#paint1_linear)"
|
||||
fillOpacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z"
|
||||
fill="#3ECF8E"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
x1="53.9738"
|
||||
y1="54.9738"
|
||||
x2="94.1635"
|
||||
y2="71.8293"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#249361" />
|
||||
<stop offset="1" stopColor="#3ECF8E" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear"
|
||||
x1="36.1558"
|
||||
y1="30.5779"
|
||||
x2="54.4844"
|
||||
y2="65.0804"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop />
|
||||
<stop offset="1" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</Link>
|
||||
<ThemeSwitcherDropdown />
|
||||
</div>
|
||||
<Link href="/" className="mb-4 block">
|
||||
<h1>Supabase Library</h1>
|
||||
</Link>
|
||||
{/* <TopNavigationSearch /> */}
|
||||
<CommandMenu />
|
||||
</div>
|
||||
<div className="pb-6 space-y-0.5">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{gettingStarted.title}
|
||||
</div>
|
||||
{gettingStarted.items.map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="pb-6 space-y-0.5">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{starterApps.title}
|
||||
</div>
|
||||
{starterApps.items.map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="pb-6">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
UI Blocks
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
{/* Render items based on component definitions */}
|
||||
{componentPages.items.map((component, i) => (
|
||||
<NavigationItem
|
||||
item={component}
|
||||
key={`${component.href?.toString() || component.title}-${i}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-6">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{oauthBlocks.title}
|
||||
</div>
|
||||
{oauthBlocks.items.map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="pb-6">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{mcpBlocks.title}
|
||||
</div>
|
||||
{mcpBlocks.items.map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="pb-6">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{platformBlocks.title}
|
||||
</div>
|
||||
{platformBlocks.items.map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideNavigation
|
||||
@@ -1,32 +1,32 @@
|
||||
import { Menu } from 'lucide-react'
|
||||
import { Button, ScrollArea, SheetContent, SheetTrigger } from 'ui'
|
||||
import { Button, ScrollArea, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from 'ui'
|
||||
|
||||
import { MobileMenuSheet } from './mobile-menu-sheet'
|
||||
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
|
||||
import SideNavigation from '@/components/side-navigation'
|
||||
import { SideNavigation } from '@/components/side-navigation'
|
||||
|
||||
export function Sidebar() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden fixed top-0 left-0 right-0 z-50 bg-background justify-between flex items-center px-8 py-3 border-b">
|
||||
<MobileMenuSheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline" icon={<Menu />} />
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="p-0 w-80" showClose={false}>
|
||||
<ScrollArea className="h-full">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</MobileMenuSheet>
|
||||
<ThemeSwitcherDropdown />
|
||||
</div>
|
||||
|
||||
<aside className="fixed z-30 top-0 hidden h-screen w-full shrink-0 md:sticky md:block bg-200 border-r border-muted/50">
|
||||
<ScrollArea className="h-full">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
</>
|
||||
<div className="md:hidden">
|
||||
<MobileMenuSheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="tiny"
|
||||
className="px-2"
|
||||
icon={<Menu size={18} />}
|
||||
aria-label="Open library navigation"
|
||||
/>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-80 max-w-[90vw] p-0">
|
||||
<SheetTitle className="sr-only">Library navigation</SheetTitle>
|
||||
<SheetDescription className="sr-only">
|
||||
Browse guides and blocks by category.
|
||||
</SheetDescription>
|
||||
<ScrollArea className="h-full">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</MobileMenuSheet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="py-6 px-4 md:px-8 md:py-0 mx-auto w-full max-w-site">
|
||||
<footer className="py-6 px-4 md:px-8 md:py-0 mx-auto w-full max-w-7xl">
|
||||
<div className="flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<p className="text-balance text-center text-sm leading-loose text-foreground-muted md:text-left">
|
||||
Built by{' '}
|
||||
@@ -14,7 +14,7 @@ export function SiteFooter() {
|
||||
</a>
|
||||
. The source code is available on{' '}
|
||||
<a
|
||||
href="https://github.com/supabase/supabase/tree/master/apps/library"
|
||||
href="https://github.com/supabase/supabase/tree/master/apps/ui-library"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
|
||||
@@ -23,27 +23,6 @@ export function SiteFooter() {
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p className="text-balance text-center text-sm leading-loose text-foreground-muted">
|
||||
Site inspired by{' '}
|
||||
<a
|
||||
href="https://www.radix-ui.com/themes/docs/overview/getting-started"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
|
||||
>
|
||||
Radix
|
||||
</a>
|
||||
,{' '}
|
||||
<a
|
||||
href="https://ui.shadcn.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
|
||||
>
|
||||
shadcn/ui
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
'use client'
|
||||
|
||||
import supabaseLogoIcon from 'common/assets/images/supabase-logo-icon.svg'
|
||||
import { ArrowUpRight, ChevronRight, Code2, Grid2X2 } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
buttonVariants,
|
||||
cn,
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import { CommandMenu } from '@/components/command-menu'
|
||||
import { Sidebar } from '@/components/sidebar'
|
||||
import { ThemeSwitcherDropdown } from '@/components/theme-switcher-dropdown'
|
||||
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
import { useMenuAim } from '@/hooks/use-menu-aim'
|
||||
|
||||
export function SiteHeader() {
|
||||
const { framework } = useFramework()
|
||||
const [activeCategory, setActiveCategory] = useState<string>(libraryCategories[0].name)
|
||||
|
||||
const menuAim = useMenuAim(activeCategory, setActiveCategory)
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-24 before:bg-gradient-to-b before:from-background before:via-background/90 before:via-60% before:to-transparent">
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:bg-background focus:p-3"
|
||||
>
|
||||
Skip to content
|
||||
</a>
|
||||
<div className="relative mx-auto flex h-14 max-w-site items-center justify-between gap-4 px-4 md:px-8">
|
||||
<div className="flex shrink-0 items-center gap-4 lg:gap-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 text-sm font-medium tracking-tight"
|
||||
aria-label="Supabase Library home"
|
||||
>
|
||||
<Image src={supabaseLogoIcon} alt="" width={24} height={24} />
|
||||
<span aria-hidden="true" className="text-border">
|
||||
/
|
||||
</span>
|
||||
<span className="text-foreground-light">Library</span>
|
||||
</Link>
|
||||
</div>
|
||||
<nav aria-label="Main navigation" className="absolute left-1/2 -translate-x-1/2">
|
||||
<NavigationMenu
|
||||
delayDuration={0}
|
||||
className="hidden h-14 md:flex md:flex-none [&>div:last-child]:left-1/2 [&>div:last-child]:-translate-x-1/2"
|
||||
viewportClassName="rounded-xl bg-background data-[state=open]:slide-in-from-right-0!"
|
||||
>
|
||||
<NavigationMenuList className="flex-none gap-1">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'text', size: 'small' }),
|
||||
'h-8 rounded-full px-3 text-foreground-light hover:text-foreground data-[state=open]:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Grid2X2 aria-hidden="true" className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium text-foreground">Explore</span>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<Tabs
|
||||
value={activeCategory}
|
||||
onValueChange={menuAim.selectCategory}
|
||||
onPointerMove={menuAim.onPointerMove}
|
||||
onPointerLeave={menuAim.onPointerLeave}
|
||||
orientation="vertical"
|
||||
className="flex w-[640px] max-w-[calc(100vw-4rem)] max-h-[calc(100dvh-5rem)] overflow-y-auto"
|
||||
>
|
||||
<div className="flex w-60 shrink-0 flex-col border-r bg-surface-75 py-2">
|
||||
<TabsList
|
||||
aria-label="Block categories"
|
||||
className="h-auto flex-col items-stretch justify-start border-b-0"
|
||||
>
|
||||
{libraryCategories.map((category) => (
|
||||
<TabsTrigger
|
||||
key={category.name}
|
||||
value={category.name}
|
||||
data-menu-category={category.name}
|
||||
className="justify-between gap-3 whitespace-normal border-0 px-4 py-2.5 text-left data-[state=active]:bg-surface-200 data-[state=active]:shadow-none"
|
||||
>
|
||||
{category.name}
|
||||
<ChevronRight
|
||||
size={14}
|
||||
className="shrink-0 text-foreground-lighter group-data-[state=active]:text-primary"
|
||||
/>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<ul className="mx-4 mt-5 flex flex-col border-t pt-2.5">
|
||||
{[
|
||||
{ href: '/', label: 'Browse all blocks' },
|
||||
{ href: '/docs/getting-started/quickstart', label: 'Quickstart' },
|
||||
{ href: '/docs/getting-started/faq', label: 'FAQ' },
|
||||
].map(({ href, label }) => (
|
||||
<li key={href}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={href}
|
||||
className="block py-1.5 text-xs text-foreground-light hover:text-foreground"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{libraryCategories.map((category) => {
|
||||
const blocks = libraryBlocks.filter(
|
||||
(block) => block.category === category.name
|
||||
)
|
||||
return (
|
||||
<TabsContent
|
||||
key={category.name}
|
||||
value={category.name}
|
||||
className="m-0 min-w-0 flex-1 p-6"
|
||||
>
|
||||
{blocks.length > 0 ? (
|
||||
<ul className="flex flex-col gap-4">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.slug}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={getLibraryBlockHref(block, framework)}
|
||||
className="group/menu-item flex h-fit flex-col gap-1 rounded-md text-sm text-foreground-light hover:text-foreground select-none leading-none no-underline focus-ring focus-visible:text-foreground"
|
||||
>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-sm leading-snug text-foreground">
|
||||
{block.title}
|
||||
</span>
|
||||
<ChevronRight
|
||||
strokeWidth={2}
|
||||
className="h-3 w-3 text-foreground transition-all -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100 motion-reduce:transition-none motion-reduce:translate-x-0"
|
||||
/>
|
||||
</span>
|
||||
<span className="text-xs leading-snug text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
{block.description}
|
||||
</span>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-base text-foreground-light">
|
||||
No blocks in this category yet.
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</nav>
|
||||
{/* Below md the centered nav is hidden, so search can take the free space. */}
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end gap-2 md:flex-none">
|
||||
<div className="min-w-0 max-w-48 flex-1 md:w-28 md:flex-none lg:w-48">
|
||||
<CommandMenu />
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
variant="text"
|
||||
size="tiny"
|
||||
className="hidden h-8 w-8 p-0 text-muted-foreground hover:text-foreground sm:inline-flex"
|
||||
aria-label="View source on GitHub"
|
||||
>
|
||||
<a href="https://github.com/supabase/supabase/tree/master/apps/ui-library">
|
||||
<Code2 size={16} />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
variant="text"
|
||||
size="small"
|
||||
className="hidden h-8 px-3 text-sm font-medium text-muted-foreground hover:text-foreground sm:inline-flex"
|
||||
iconRight={<ArrowUpRight size={14} />}
|
||||
>
|
||||
<a href="https://supabase.com/docs">Docs</a>
|
||||
</Button>
|
||||
<ThemeSwitcherDropdown />
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -48,7 +48,8 @@ const ThemeSwitcherDropdown = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const iconClasses = 'text-foreground-light group-data-open:text-foreground'
|
||||
const iconClasses =
|
||||
'text-muted-foreground group-hover:text-foreground group-data-open:text-foreground'
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -57,7 +58,8 @@ const ThemeSwitcherDropdown = () => {
|
||||
<Button
|
||||
variant="text"
|
||||
size="tiny"
|
||||
className="px-1 group"
|
||||
aria-label="Choose a theme"
|
||||
className="group h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
||||
icon={
|
||||
resolvedTheme?.includes('light') ? (
|
||||
<Sun className={iconClasses} />
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
// @ts-nocheck
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import { useMounted } from '@/hooks/use-mounted'
|
||||
import { TableOfContents } from '@/lib/toc'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface TocProps {
|
||||
toc: TableOfContents
|
||||
}
|
||||
|
||||
export function DashboardTableOfContents({ toc }: TocProps) {
|
||||
const itemIds = React.useMemo(
|
||||
() =>
|
||||
toc.items
|
||||
? toc.items
|
||||
.flatMap((item) => [item.url, item?.items?.map((item) => item.url)])
|
||||
.flat()
|
||||
.filter(Boolean)
|
||||
.map((id) => id?.split('#')[1])
|
||||
: [],
|
||||
[toc]
|
||||
)
|
||||
const activeHeading = useActiveItem(itemIds)
|
||||
const mounted = useMounted()
|
||||
|
||||
if (!toc?.items || !mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium text-foreground-light">On This Page</p>
|
||||
<Tree tree={toc} activeItem={activeHeading} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function useActiveItem(itemIds: string[]) {
|
||||
const [activeId, setActiveId] = React.useState(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveId(entry.target.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
{ rootMargin: `0% 0% -80% 0%` }
|
||||
)
|
||||
|
||||
itemIds?.forEach((id) => {
|
||||
const element = document.getElementById(id)
|
||||
if (element) {
|
||||
observer.observe(element)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
itemIds?.forEach((id) => {
|
||||
const element = document.getElementById(id)
|
||||
if (element) {
|
||||
observer.unobserve(element)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [itemIds])
|
||||
|
||||
return activeId
|
||||
}
|
||||
|
||||
interface TreeProps {
|
||||
tree: TableOfContents
|
||||
level?: number
|
||||
activeItem?: string
|
||||
}
|
||||
|
||||
function Tree({ tree, level = 1, activeItem }: TreeProps) {
|
||||
return tree?.items?.length && level < 3 ? (
|
||||
<ul className={cn('m-0 list-none', { 'pl-4': level !== 1 })}>
|
||||
{tree.items.map((item, index) => {
|
||||
return (
|
||||
<li key={index} className={cn('mt-0 pt-2')}>
|
||||
<a
|
||||
href={item.url}
|
||||
className={cn(
|
||||
'inline-block no-underline transition-colors hover:text-foreground',
|
||||
item.url === `#${activeItem}`
|
||||
? 'font-medium text-foreground'
|
||||
: 'text-foreground-muted'
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</a>
|
||||
{item.items?.length ? (
|
||||
<Tree tree={item} level={level + 1} activeItem={activeItem} />
|
||||
) : null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : null
|
||||
}
|
||||
@@ -10,10 +10,10 @@ export const gettingStarted: SidebarNavGroup = {
|
||||
commandItemLabel: 'Introduction',
|
||||
},
|
||||
{
|
||||
title: 'Quick Start',
|
||||
title: 'Quickstart',
|
||||
href: '/docs/getting-started/quickstart',
|
||||
items: [],
|
||||
commandItemLabel: 'Quick Start',
|
||||
commandItemLabel: 'Quickstart',
|
||||
},
|
||||
{
|
||||
title: 'FAQ',
|
||||
@@ -24,40 +24,6 @@ export const gettingStarted: SidebarNavGroup = {
|
||||
],
|
||||
}
|
||||
|
||||
export const starterApps: SidebarNavGroup = {
|
||||
title: 'Starter Apps',
|
||||
items: [
|
||||
{
|
||||
title: 'Next.js Starter',
|
||||
href: '/docs/starters/nextjs-starter',
|
||||
items: [],
|
||||
new: true,
|
||||
commandItemLabel: 'Next.js Starter',
|
||||
},
|
||||
{
|
||||
title: 'SaaS Starter',
|
||||
href: '/docs/starters/saas-starter',
|
||||
items: [],
|
||||
new: true,
|
||||
commandItemLabel: 'SaaS Starter',
|
||||
},
|
||||
{
|
||||
title: 'AI Chat App',
|
||||
href: '/docs/starters/ai-chat-app',
|
||||
items: [],
|
||||
new: true,
|
||||
commandItemLabel: 'AI Chat App',
|
||||
},
|
||||
{
|
||||
title: 'Flutter Starter',
|
||||
href: '/docs/starters/flutter-starter',
|
||||
items: [],
|
||||
new: true,
|
||||
commandItemLabel: 'Flutter Starter',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const platformBlocks: SidebarNavGroup = {
|
||||
title: 'Platform',
|
||||
items: [
|
||||
@@ -193,29 +159,6 @@ export const componentPages: SidebarNavGroup = {
|
||||
],
|
||||
}
|
||||
|
||||
export const COMMAND_ITEMS = [
|
||||
...gettingStarted.items.map((item) => ({
|
||||
label: item.commandItemLabel,
|
||||
href: item.href,
|
||||
})),
|
||||
...starterApps.items.map((item) => ({
|
||||
label: item.commandItemLabel,
|
||||
href: item.href,
|
||||
})),
|
||||
...componentPages.items.map((item) => ({
|
||||
label: item.commandItemLabel,
|
||||
href: item.href,
|
||||
})),
|
||||
...oauthBlocks.items.map((item) => ({
|
||||
label: item.commandItemLabel,
|
||||
href: item.href,
|
||||
})),
|
||||
...mcpBlocks.items.map((item) => ({
|
||||
label: item.commandItemLabel,
|
||||
href: item.href,
|
||||
})),
|
||||
]
|
||||
|
||||
// Framework titles for display
|
||||
export const frameworkTitles: Record<string, string> = {
|
||||
nextjs: 'Next.js',
|
||||
|
||||
@@ -1,94 +1,134 @@
|
||||
---
|
||||
title: FAQ
|
||||
description: Frequently asked questions
|
||||
title: Frequently Asked Questions
|
||||
description: Exploring, installing, and adapting Supabase Library blocks
|
||||
---
|
||||
|
||||
<Accordion openBehaviour="single" collapsible>
|
||||
<AccordionItem value="how">
|
||||
<AccordionTrigger>How does it work? </AccordionTrigger>
|
||||
<AccordionContent>
|
||||
You run a command in a new or existing project that looks something like this:
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
|
||||
```
|
||||
This will add all of the files to your project and install the dependencies.
|
||||
After you've run the command, you can start using the components. In the case of Auth, you should be up and running with user sign in/sign up with just this one command.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="overwrite">
|
||||
<AccordionTrigger>Will it overwrite existing files?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
No. If you run a command in an existing project, it will <u>not</u> overwrite your existing
|
||||
files. If there conflicting files, you will be asked what you want to do (leave them or
|
||||
overwrite them). If you run it in a new project, it will just add the new files to your project.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="existing-project">
|
||||
<AccordionTrigger>I've already started a project, what should I do?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
You can still use these components, even if you've already started your project! The commands
|
||||
won't overwrite your existing files, but you should still be careful with the changes to makes
|
||||
sure you understand what's been added. You should commit your changes into Git before running
|
||||
these commands so you'll be able to see a clean diff of the changes.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="shadcn">
|
||||
<AccordionTrigger>I've already installed shadcn/ui, what should I do?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
You're off to a great start! You can now run an add command to add the desired component to your
|
||||
project.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="quickstart">
|
||||
<AccordionTrigger>
|
||||
I already started with `npx create-next-app -e with-supabase` what should I do now?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Our Next.js quickstart is a great starting point for building a full app with Supabase and
|
||||
Next.js. If you've already started with this template, you can just add the components in this library to your project.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="quickstart-needed">
|
||||
<AccordionTrigger>
|
||||
Do I need to install one of the framework quickstarts before using this library?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
No. You don't need to start with one of our framework quickstarts.
|
||||
<div className="library-faq border-y">
|
||||
|
||||
If you already have an existing project, you can just add the components in this library to your project. If you're starting from scratch, you should use one of the shadcn/ui setup guides for your framework of choice via the [Quickstart](/library/docs/getting-started/quickstart) page.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="ai-builder">
|
||||
<AccordionTrigger>I used an AI builder to build an app, what should I do?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
If your app is already built, you can use any of the commands to drop components into your
|
||||
project. Your app likely already has a Supabase client set up, so you can just continue using
|
||||
that if it's working well for you.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="react-only">
|
||||
<AccordionTrigger>Why are many UI blocks React-only?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Several reasons.
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>What is Supabase Library?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
First, we use React ourselves and know the ecosystem really well. That makes these UI blocks easy for us to build and maintain.
|
||||
Supabase Library is a collection of blocks and starter apps for Supabase projects. Browse by category, inspect a block, and follow its installation instructions. The files live in your project, so you can adapt them to your application.
|
||||
|
||||
Second, the UI portion of the library stitches together existing shadcn/ui and internal components into Supabase-specific workflows. It builds on an established tool rather than creating a new UI framework from the ground up.
|
||||
</details>
|
||||
|
||||
Last, it's a way for us to better distribute components that we already had out there in different places (our docs and guides, different framework quickstarts, etc)
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>How do I install a block?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
TL;DR: shadcn/ui + our internal React experience make this easy to do well. Doing it for other ecosystems would be much more challenging.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="other-frameworks">
|
||||
<AccordionTrigger>Any plans for other, non-React frameworks?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Not at the moment, but we're not ruling it out.
|
||||
Open a block page, choose your framework, and run its installation command from your project directory. For example, this adds the Next.js password authentication block:
|
||||
|
||||
Our expertise is predominantly in React—that's the ecosystem in which we think we can provide the highest quality tools for our users. But we've heard from lots of people in the Vue and Svelte communities in particular, and agree something like this would be really beneficial for them.
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
|
||||
```
|
||||
|
||||
This isn't a situation like "we will never do it", but instead more of a "we don't want to reinvent the wheel". If the right opportunity comes up for supporting Vue, Svelte, etc like this, we're very open to supporting it.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
The command adds the block's files and dependencies. Follow the block's setup instructions to configure your Supabase project, environment variables, and any required redirect URLs or email templates. Vue and Nuxt blocks use `shadcn-vue` instead of `shadcn`.
|
||||
|
||||
</Accordion>
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>What does a starter app include?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Starter apps are blocks that provide a starting point for a complete application. Each starter page includes its framework, setup commands, configuration steps, and source repository. Follow the commands on that page: some starters compose library blocks, while others use an existing application template.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Will installing a block overwrite existing files?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
The block installer asks how to handle conflicting files. Review those prompts and the resulting changes before accepting replacements. Commit your current work first so you can inspect the diff and recover any changes you want to keep.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Can I use blocks in an existing project?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Yes. Use the block's framework-specific instructions and adapt its files to your project structure. If you already have a working Supabase client, reuse it instead of replacing it. You can also copy the agent prompt from the block page to help adapt the installation to your conventions.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>What if I already use shadcn/ui?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
You can run the block's add command without initializing shadcn/ui again. Keep your existing `components.json`, aliases, and theme. Review the block's prerequisites if you encounter a setup issue.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>What if I started with the Next.js Supabase template?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
The `with-supabase` template already includes a Supabase client and authentication setup. Add the blocks you need and review overlapping files before replacing them. You do not need to create another app or repeat the client setup.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Do I need a framework quickstart first?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Use the [Quickstart](/library/docs/getting-started/quickstart) if you are starting a new project. An existing project can install blocks directly once it meets the block's framework and component-library requirements.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Can I add blocks to an app built with AI?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Yes, if the app uses a supported framework. Keep any Supabase client that is already working, install the block, and adapt its components to your app. Review the changes just as you would for any other project.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Which frameworks are supported?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
The library includes blocks for Next.js, React Router, TanStack Start, React SPA, Vue, and Nuxt. Support varies by block; the framework selector shows the available versions. Starter apps can use other frameworks, such as Flutter, with their own setup instructions.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Why are some blocks only available for React?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Some blocks build on React-specific libraries and components used in Supabase's own tools. Other blocks also have Vue and Nuxt versions. Check the framework selector on the block page for its current support; a React block cannot be installed directly into a different UI framework.
|
||||
|
||||
</details>
|
||||
|
||||
<details className="group border-b py-6 last:border-b-0">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
|
||||
<span>Where can I get help?</span>
|
||||
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
|
||||
</summary>
|
||||
|
||||
Start with the [Quickstart](/library/docs/getting-started/quickstart) and the setup notes on your block's page. For Supabase configuration, see the [documentation](https://supabase.com/docs). You can report library issues in the [Supabase repository](https://github.com/supabase/supabase/issues).
|
||||
|
||||
</details>
|
||||
|
||||
</div>
|
||||
@@ -1,77 +1,452 @@
|
||||
---
|
||||
title: Quick Start
|
||||
description: Install shadcn/ui and use the components in your project
|
||||
title: Quickstart
|
||||
description: Create an app, connect Supabase, and install your first library block
|
||||
---
|
||||
|
||||
## Set up shadcn/ui
|
||||
<FrameworkQuickstart>
|
||||
|
||||
Start by installing [shadcn/ui](https://ui.shadcn.com/) in your project. Use the appropriate setup guide linked below for your framework of choice.
|
||||
<FrameworkQuickstartTab value="nextjs">
|
||||
|
||||
If you're already using a recent version of shadcn/ui in your existing project, you can skip this step—this library should work with your current setup. However, if you encounter issues, try deleting your existing `components.json` file and follow one of the guides to reset your setup.
|
||||
## Next.js
|
||||
|
||||
<div className="grid gap-4 mt-8 sm:gap-6">
|
||||
<Card>
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-10 h-10"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>Next.js</title>
|
||||
<path d="M11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728C1.1004 6.584.3802 8.3666.1082 10.255c-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 01-.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 004.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z" />
|
||||
</svg>
|
||||
<p className="font-medium mt-2 mb-6">Next.js</p>
|
||||
<div className="border rounded-md py-1 px-2 bg-muted flex items-center gap-2">
|
||||
<p>
|
||||
<span className="select-none">`$`</span>`npx create-next-app -e with-supabase`
|
||||
</p>
|
||||
<CopyButton value="npx create-next-app -e with-supabase" />
|
||||
</div>
|
||||
</Card>
|
||||
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/react-router">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-10 h-10"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12.118 5.466a2.306 2.306 0 0 0-.623.08c-.278.067-.702.332-.953.583-.41.423-.49.609-.662 1.469-.08.423.41 1.43.847 1.734.45.317 1.085.502 2.065.608 1.429.16 1.84.636 1.84 2.197 0 1.377-.385 1.747-1.96 1.906-1.707.172-2.58.834-2.765 2.117-.106.781.41 1.76 1.125 2.091 1.627.768 3.15-.198 3.467-2.196.211-1.284.622-1.642 1.998-1.747 1.588-.133 2.409-.675 2.713-1.787.278-1.02-.304-2.157-1.297-2.554-.264-.106-.873-.238-1.35-.291-1.495-.16-1.879-.424-2.038-1.39-.225-1.337-.317-1.562-.794-2.09a2.174 2.174 0 0 0-1.613-.73zm-4.785 4.36a2.145 2.145 0 0 0-.497.048c-1.469.318-2.17 2.051-1.35 3.295 1.178 1.774 3.944.953 3.97-1.177.012-1.193-.98-2.143-2.123-2.166zM2.089 14.19a2.22 2.22 0 0 0-.427.052c-2.158.476-2.237 3.626-.106 4.182.53.145.582.145 1.111.013 1.191-.318 1.866-1.456 1.549-2.607-.278-1.02-1.144-1.664-2.127-1.64zm19.824.008c-.233.002-.477.058-.784.162-1.39.477-1.866 2.092-.98 3.336.557.794 1.96 1.058 2.82.516 1.416-.874 1.363-3.057-.093-3.746-.38-.186-.663-.271-.963-.268z" />
|
||||
</svg>
|
||||
<p className="font-medium mt-2">React Router</p>
|
||||
</LinkedCard>
|
||||
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/tanstack">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
className="w-10 h-10"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M6.93 13.688a.343.343 0 0 1 .468.132l.063.106c.48.851.98 1.66 1.5 2.426a35.65 35.65 0 0 0 2.074 2.742.345.345 0 0 1-.039.484l-.074.066c-2.543 2.223-4.191 2.665-4.953 1.333-.746-1.305-.477-3.672.808-7.11a.344.344 0 0 1 .153-.18ZM17.75 16.3a.34.34 0 0 1 .395.27l.02.1c.628 3.286.187 4.93-1.325 4.93-1.48 0-3.36-1.402-5.649-4.203a.327.327 0 0 1-.074-.222c0-.188.156-.34.344-.34h.121a32.984 32.984 0 0 0 2.809-.098c1.07-.086 2.191-.23 3.359-.437zm.871-6.977a.353.353 0 0 1 .445-.21l.102.034c3.262 1.11 4.504 2.332 3.719 3.664-.766 1.305-2.993 2.254-6.684 2.848a.362.362 0 0 1-.238-.047.343.343 0 0 1-.125-.476l.062-.106a34.07 34.07 0 0 0 1.367-2.523c.477-.989.93-2.051 1.352-3.184zM7.797 8.34a.362.362 0 0 1 .238.047.343.343 0 0 1 .125.476l-.062.106a34.088 34.088 0 0 0-1.367 2.523c-.477.988-.93 2.051-1.352 3.184a.353.353 0 0 1-.445.21l-.102-.034C1.57 13.742.328 12.52 1.113 11.188 1.88 9.883 4.106 8.934 7.797 8.34Zm5.281-3.984c2.543-2.223 4.192-2.664 4.953-1.332.746 1.304.477 3.671-.808 7.109a.344.344 0 0 1-.153.18.343.343 0 0 1-.468-.133l-.063-.106a34.64 34.64 0 0 0-1.5-2.426 35.65 35.65 0 0 0-2.074-2.742.345.345 0 0 1 .039-.484ZM7.285 2.274c1.48 0 3.364 1.402 5.649 4.203a.349.349 0 0 1 .078.218.348.348 0 0 1-.348.344l-.117-.004a34.584 34.584 0 0 0-2.809.102 35.54 35.54 0 0 0-3.363.437.343.343 0 0 1-.394-.273l-.02-.098c-.629-3.285-.188-4.93 1.324-4.93Zm2.871 5.812h3.688a.638.638 0 0 1 .55.316l1.848 3.22a.644.644 0 0 1 0 .628l-1.847 3.223a.638.638 0 0 1-.551.316h-3.688a.627.627 0 0 1-.547-.316L7.758 12.25a.644.644 0 0 1 0-.629L9.61 8.402a.627.627 0 0 1 .546-.316Zm3.23.793a.638.638 0 0 1 .552.316l1.39 2.426a.644.644 0 0 1 0 .629l-1.39 2.43a.638.638 0 0 1-.551.316h-2.774a.627.627 0 0 1-.546-.316l-1.395-2.43a.644.644 0 0 1 0-.629l1.395-2.426a.627.627 0 0 1 .546-.316Zm-.491.867h-1.79a.624.624 0 0 0-.546.316l-.899 1.56a.644.644 0 0 0 0 .628l.899 1.563a.632.632 0 0 0 .547.316h1.789a.632.632 0 0 0 .547-.316l.898-1.563a.644.644 0 0 0 0-.629l-.898-1.558a.624.624 0 0 0-.547-.317Zm-.477.828c.227 0 .438.121.547.317l.422.73a.625.625 0 0 1 0 .629l-.422.734a.627.627 0 0 1-.547.317h-.836a.632.632 0 0 1-.547-.317l-.422-.734a.625.625 0 0 1 0-.629l.422-.73a.632.632 0 0 1 .547-.317zm-.418.817a.548.548 0 0 0-.473.273.547.547 0 0 0 0 .547.544.544 0 0 0 .473.27.544.544 0 0 0 .473-.27.547.547 0 0 0 0-.547.548.548 0 0 0-.473-.273Zm-4.422.546h.98M18.98 7.75c.391-1.895.477-3.344.223-4.398-.148-.63-.422-1.137-.84-1.508-.441-.39-1-.582-1.625-.582-1.035 0-2.12.472-3.281 1.367a14.9 14.9 0 0 0-1.473 1.316 1.206 1.206 0 0 0-.136-.144c-1.446-1.285-2.66-2.082-3.7-2.39-.617-.184-1.195-.2-1.722-.024-.559.187-1.004.574-1.317 1.117-.515.894-.652 2.074-.46 3.527.078.59.214 1.235.402 1.934a1.119 1.119 0 0 0-.215.047C3.008 8.62 1.71 9.269.926 10.015c-.465.442-.77.938-.883 1.481-.113.578 0 1.156.312 1.7.516.894 1.465 1.597 2.817 2.155.543.223 1.156.426 1.844.61a1.023 1.023 0 0 0-.07.226c-.391 1.891-.477 3.344-.223 4.395.148.629.425 1.14.84 1.508.44.39 1 .582 1.625.582 1.035 0 2.12-.473 3.28-1.364.477-.37.973-.816 1.489-1.336a1.2 1.2 0 0 0 .195.227c1.446 1.285 2.66 2.082 3.7 2.39.617.184 1.195.2 1.722.024.559-.187 1.004-.574 1.317-1.117.515-.894.652-2.074.46-3.527a14.941 14.941 0 0 0-.425-2.012 1.225 1.225 0 0 0 .238-.047c1.828-.61 3.125-1.258 3.91-2.004.465-.441.77-.937.883-1.48.113-.578 0-1.157-.313-1.7-.515-.894-1.464-1.597-2.816-2.156a14.576 14.576 0 0 0-1.906-.625.865.865 0 0 0 .059-.195z" />
|
||||
</svg>
|
||||
<p className="font-medium mt-2">TanStack Start</p>
|
||||
</LinkedCard>
|
||||
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/manual">
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-10 h-10"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>React SPA</title>
|
||||
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
|
||||
</svg>
|
||||
<p className="font-medium mt-2">React SPA</p>
|
||||
</LinkedCard>
|
||||
</div>
|
||||
<QuickstartStep number="1">
|
||||
|
||||
<TanStackBeta />
|
||||
### Create the app
|
||||
|
||||
When you've finished setting up shadcn/ui, you can start exploring the components in the sidebar
|
||||
Create a [Next.js project](https://ui.shadcn.com/docs/installation/next) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init --template next --name my-app --base radix
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [Next.js client](/library/docs/nextjs/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/supabase-client-nextjs.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
||||
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
|
||||
```
|
||||
|
||||
Follow the [Next.js authentication setup](/library/docs/nextjs/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
<FrameworkQuickstartTab value="react-router">
|
||||
|
||||
## React Router
|
||||
|
||||
<QuickstartStep number="1">
|
||||
|
||||
### Create the app
|
||||
|
||||
Create a [React Router project](https://ui.shadcn.com/docs/installation/react-router) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init --template react-router --name my-app --base radix
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [React Router client](/library/docs/react-router/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/supabase-client-react-router.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=your-project-url
|
||||
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-react-router.json
|
||||
```
|
||||
|
||||
Follow the [React Router authentication setup](/library/docs/react-router/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
<FrameworkQuickstartTab value="tanstack">
|
||||
|
||||
## TanStack Start
|
||||
|
||||
<QuickstartStep number="1">
|
||||
|
||||
### Create the app
|
||||
|
||||
Create a [TanStack Start project](https://ui.shadcn.com/docs/installation/tanstack) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init --template start --name my-app --base radix
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [TanStack Start client](/library/docs/tanstack/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/supabase-client-tanstack.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=your-project-url
|
||||
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-tanstack.json
|
||||
```
|
||||
|
||||
Follow the [TanStack Start authentication setup](/library/docs/tanstack/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
Check the [TanStack Start guide](https://tanstack.com/start/latest/docs/framework/react/getting-started) for framework setup changes if the generated routes differ from the block examples.
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
<FrameworkQuickstartTab value="react">
|
||||
|
||||
## React SPA
|
||||
|
||||
<QuickstartStep number="1">
|
||||
|
||||
### Create the app
|
||||
|
||||
Create a [React SPA project](https://ui.shadcn.com/docs/installation/vite) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init --template vite --name my-app --base radix
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [React SPA client](/library/docs/react/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/supabase-client-react.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=your-project-url
|
||||
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-react.json
|
||||
```
|
||||
|
||||
Follow the [React SPA authentication setup](/library/docs/react/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
<FrameworkQuickstartTab value="vue">
|
||||
|
||||
## Vue
|
||||
|
||||
<QuickstartStep number="1">
|
||||
|
||||
### Create the app
|
||||
|
||||
Create a [Vue project](https://www.shadcn-vue.com/docs/installation/vite) with Tailwind CSS, import aliases, and shadcn-vue configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest init --template vite --name my-app
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn-vue@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [Vue client](/library/docs/vue/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add https://supabase.com/library/r/supabase-client-vue.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=your-project-url
|
||||
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add https://supabase.com/library/r/password-based-auth-vue.json
|
||||
```
|
||||
|
||||
Follow the [Vue authentication setup](/library/docs/vue/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
<FrameworkQuickstartTab value="nuxtjs">
|
||||
|
||||
## Nuxt.js
|
||||
|
||||
<QuickstartStep number="1">
|
||||
|
||||
### Create the app
|
||||
|
||||
Create a [Nuxt.js project](https://www.shadcn-vue.com/docs/installation/nuxt) with Tailwind CSS, import aliases, and shadcn-vue configured. Choose npm when prompted for a package manager.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest init --template nuxt --name my-app
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Already have a project? Run `npx shadcn-vue@latest init` from its root if it does not have a `components.json` file.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="2">
|
||||
|
||||
### Install the Supabase client
|
||||
|
||||
Add the [Nuxt.js client](/library/docs/nuxtjs/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add https://supabase.com/library/r/supabase-client-nuxtjs.json
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="3">
|
||||
|
||||
### Configure Supabase
|
||||
|
||||
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
|
||||
|
||||
```env
|
||||
NUXT_PUBLIC_SUPABASE_URL=your-project-url
|
||||
NUXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="4">
|
||||
|
||||
### Add your first block
|
||||
|
||||
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add https://supabase.com/library/r/password-based-auth-nuxtjs.json
|
||||
```
|
||||
|
||||
Follow the [Nuxt.js authentication setup](/library/docs/nuxtjs/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
<QuickstartStep number="5">
|
||||
|
||||
### Start developing
|
||||
|
||||
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</QuickstartStep>
|
||||
|
||||
</FrameworkQuickstartTab>
|
||||
|
||||
</FrameworkQuickstart>
|
||||
|
||||
## Explore the library
|
||||
|
||||
Browse [blocks](/library) by category to find your next component. Each block page includes installation commands, source files, and framework-specific usage instructions.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues, try deleting your existing `components.json` file and follow one of the guides to reset your setup.
|
||||
Run install commands from the directory containing `components.json`. If an import cannot be resolved, check its aliases and the paths in your TypeScript configuration. For an existing project, use the setup guide linked in your framework tab to check Tailwind CSS and alias configuration before reinitializing shadcn/ui.
|
||||
|
||||
For issues and questions about specific frameworks, see the [FAQs](/library/docs/getting-started/faq)
|
||||
For framework-specific questions, see the [FAQs](/library/docs/getting-started/faq).
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: MCP Server
|
||||
description: Add a user-scoped MCP server to your product
|
||||
preview: <BlockOverview name="mcp-server" showFiles />
|
||||
---
|
||||
|
||||
Give embedded product agents and external clients such as Codex, Claude Code,
|
||||
@@ -19,10 +20,6 @@ root even when the frontend uses `src/`.
|
||||
In a frontend app, add `"supabase/functions/**"` to the app's `tsconfig.json` `exclude`
|
||||
list, preserving existing entries. Check the function separately with Deno.
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="mcp-server" />
|
||||
|
||||
## Configure the project
|
||||
|
||||
The function verifies access tokens itself, so disable the gateway JWT check:
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
title: Supabase Client Libraries
|
||||
description: Supabase client for Next.js
|
||||
preview: <BlockOverview name="supabase-client-nextjs" showFiles />
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="supabase-client-nextjs" description="Supabase Client for Next.js" />
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-nextjs" />
|
||||
<BlockItem name="supabase-client-nextjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Current User Avatar
|
||||
description: Supabase Auth-aware avatar
|
||||
preview: |-
|
||||
<BlockOverview name="current-user-avatar-nextjs" showFiles>
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="current-user-avatar-nextjs"
|
||||
description="Renders the avatar of the current user."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="current-user-avatar-nextjs" />
|
||||
<BlockItem name="current-user-avatar-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
---
|
||||
title: Dropzone (File Upload)
|
||||
description: Displays a control for easier uploading of files directly to Supabase Storage
|
||||
preview: |-
|
||||
<BlockOverview name="dropzone-nextjs" showFiles>
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
|
||||
showCode={false}
|
||||
/>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
showCode={false}
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="dropzone-nextjs"
|
||||
description="Displays a control for easier uploading of files directly to Supabase Storage"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="dropzone-nextjs" />
|
||||
<BlockItem name="dropzone-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: OAuth Consent
|
||||
description: OAuth 2.1 consent block for Next.js
|
||||
preview: |-
|
||||
<BlockOverview name="oauth-consent-nextjs" showFiles>
|
||||
<BlockPreview name="oauth-consent" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="oauth-consent" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="oauth-consent-nextjs"
|
||||
description="OAuth 2.1 consent screen and the /oauth/consent route"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="oauth-consent-nextjs" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="oauth-consent-nextjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Password-based Authentication
|
||||
description: Password-based authentication block for Next.js
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-nextjs" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="password-based-auth-nextjs"
|
||||
description="All needed components for the password based auth flow"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="password-based-auth-nextjs" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-nextjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Avatar Stack
|
||||
description: Avatar stack in realtime
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-avatar-stack-nextjs" showFiles>
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-avatar-stack-nextjs"
|
||||
description="Renders a stack of avatars which are connected via Supabase Realtime"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-avatar-stack-nextjs" />
|
||||
<BlockItem name="realtime-avatar-stack-nextjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Chat
|
||||
description: Real-time chat component for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-chat-nextjs" showFiles>
|
||||
<DualRealtimeChat />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeChat />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-chat-nextjs"
|
||||
description="Renders a real-time chat interface for users in a shared room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-chat-nextjs" />
|
||||
<BlockItem name="realtime-chat-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Realtime Cursor
|
||||
description: Real-time cursor sharing for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-cursor-nextjs" showFiles>
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-cursor-nextjs"
|
||||
description="Renders realtime cursors from other users in a room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-cursor-nextjs" />
|
||||
<BlockItem name="realtime-cursor-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Flow
|
||||
description: Real-time flow diagram editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-flow-nextjs" showFiles>
|
||||
<DualRealtimeFlow />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeFlow />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-flow-nextjs"
|
||||
description="Real-time flow diagram editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-flow-nextjs" />
|
||||
<BlockItem name="realtime-flow-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
type Node,
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
type Edge,
|
||||
type Node,
|
||||
} from '@xyflow/react'
|
||||
|
||||
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Monaco
|
||||
description: Real-time Monaco editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-monaco-nextjs" showFiles>
|
||||
<DualRealtimeMonaco />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeMonaco />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-monaco-nextjs"
|
||||
description="Real-time Monaco editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-monaco-nextjs" />
|
||||
<BlockItem name="realtime-monaco-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for Next.js
|
||||
preview: |-
|
||||
<BlockOverview name="social-auth-nextjs" showFiles>
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using Github provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
@@ -12,14 +14,10 @@ description: Social authentication block for Next.js
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="social-auth-nextjs" description="All needed components for the social auth flow" />
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="social-auth-nextjs" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="social-auth-nextjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a social authentication flow.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: TanStack DB
|
||||
description: Local-first, reactive collections backed by Supabase
|
||||
description: 'Local-first, reactive collections backed by Supabase'
|
||||
preview: <BlockOverview name="tanstack-db-nextjs" />
|
||||
---
|
||||
|
||||
A [TanStack/db](https://tanstack.com/db/latest) collection backed by [Supabase](https://supabase.com/). It wires up Supabase PostgREST queries, mutations, and real-time subscriptions so you get a reactive state that stays in sync with your Postgres database.
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
---
|
||||
title: Supabase Client Libraries
|
||||
description: Supabase client for Nuxt.js
|
||||
preview: <BlockOverview name="supabase-client-nuxtjs" showFiles />
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="supabase-client-nuxtjs"
|
||||
description="Supabase Client for Nuxt.js"
|
||||
framework="vue"
|
||||
/>
|
||||
If your project already has a Supabase client, reuse it instead of running this command. Otherwise, install the client and configure the environment variables below.
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-nuxtjs" />
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
---
|
||||
title: Current User Avatar
|
||||
description: Supabase Auth-aware avatar
|
||||
preview: |-
|
||||
<BlockOverview name="current-user-avatar-nuxtjs" showFiles>
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="current-user-avatar-nuxtjs"
|
||||
description="Renders the avatar of the current user."
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Vue from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="current-user-avatar-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="current-user-avatar-nuxtjs" framework="vue" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
---
|
||||
title: Dropzone (File Upload)
|
||||
description: Displays a control for easier uploading of files directly to Supabase Storage
|
||||
preview: |-
|
||||
<BlockOverview name="dropzone-nuxtjs" showFiles>
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone component that allows users to drag-and-drop files or select them manually. It supports multiple files, displays upload progress, previews images, and shows validation errors."
|
||||
showCode={false}
|
||||
/>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone component that allows users to drag-and-drop files or select them manually. It supports multiple files, displays upload progress, previews images, and shows validation errors."
|
||||
showCode={false}
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="dropzone-nuxtjs"
|
||||
description="Displays a control for easier uploading of files directly to Supabase Storage"
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="dropzone-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="dropzone-nuxtjs" framework="vue" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
---
|
||||
title: Password-based Authentication
|
||||
description: Password-based authentication block for Nuxt.js
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-nuxtjs" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="password-based-auth-nuxtjs"
|
||||
description="All needed components for the password based auth flow"
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="password-based-auth-nuxtjs" framework="vue" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
Once you install the block in your Nuxt.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
### Getting started
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
---
|
||||
title: Realtime Avatar Stack
|
||||
description: Avatar stack in realtime
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-avatar-stack-nuxtjs" showFiles>
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-avatar-stack-nuxtjs"
|
||||
description="Renders a stack of avatars which are connected via Supabase Realtime"
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="realtime-avatar-stack-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="realtime-avatar-stack-nuxtjs" framework="vue" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
---
|
||||
title: Realtime Chat
|
||||
description: Real-time chat component for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-chat-nuxtjs" showFiles>
|
||||
<DualRealtimeChat />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeChat />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-chat-nuxtjs"
|
||||
description="Renders a real-time chat interface for users in a shared room."
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Vue from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="realtime-chat-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="realtime-chat-nuxtjs" framework="vue" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
---
|
||||
title: Realtime Cursor
|
||||
description: Real-time cursor sharing for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-cursor-nuxtjs" showFiles>
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-cursor-nuxtjs"
|
||||
description="Renders realtime cursors from other users in a room."
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="realtime-cursor-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="realtime-cursor-nuxtjs" framework="vue" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for Nuxt.js
|
||||
preview: |-
|
||||
<BlockOverview name="social-auth-nuxtjs" showFiles>
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using Github provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
@@ -12,17 +14,17 @@ description: Social authentication block for Nuxt.js
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="social-auth-nuxtjs"
|
||||
description="All needed components for the social auth flow"
|
||||
framework="vue"
|
||||
/>
|
||||
### Set up the Supabase client
|
||||
|
||||
## Folder structure
|
||||
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
|
||||
|
||||
<RegistryBlock itemName="social-auth-nuxtjs" />
|
||||
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
|
||||
|
||||
### Install the block
|
||||
|
||||
<BlockItem name="social-auth-nuxtjs" framework="vue" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
---
|
||||
title: Platform Kit
|
||||
description: The easiest way to build platforms on top of Supabase
|
||||
preview: |-
|
||||
<BlockOverview name="platform-kit-nextjs" showFiles>
|
||||
<video width="100%" muted autoPlay controls>
|
||||
<source
|
||||
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/marketing/blog/platform-kit/platform-kit.mp4"
|
||||
type="video/mp4"
|
||||
/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<video width="100%" muted autoPlay controls>
|
||||
<source
|
||||
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/marketing/blog/platform-kit/platform-kit.mp4"
|
||||
type="video/mp4"
|
||||
/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="platform-kit-nextjs"
|
||||
description="Embeddable dialog for managing Supabase projects via the Management API."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="platform-kit-nextjs" />
|
||||
<BlockItem name="platform-kit-nextjs" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -68,7 +63,7 @@ export default function Example() {
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## Quickstart
|
||||
|
||||
1. **Set up environment variables:** in your `.env.local` file:
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
title: Supabase Client Libraries
|
||||
description: Supabase client for React Router
|
||||
preview: <BlockOverview name="supabase-client-react-router" showFiles />
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="supabase-client-react-router" description="Supabase Client for React Router" />
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-react-router" />
|
||||
<BlockItem name="supabase-client-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Current User Avatar
|
||||
description: Supabase Auth-aware avatar
|
||||
preview: |-
|
||||
<BlockOverview name="current-user-avatar-react-router" showFiles>
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="current-user-avatar-react-router"
|
||||
description="Renders the avatar of the current user."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="current-user-avatar-react-router" />
|
||||
<BlockItem name="current-user-avatar-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
---
|
||||
title: Dropzone (File Upload)
|
||||
description: Displays a control for easier uploading of files directly to Supabase Storage
|
||||
preview: |-
|
||||
<BlockOverview name="dropzone-react-router" showFiles>
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
|
||||
showCode={false}
|
||||
/>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
showCode={false}
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="dropzone-react-router"
|
||||
description="Displays a control for easier uploading of files directly to Supabase Storage"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="dropzone-react-router" />
|
||||
<BlockItem name="dropzone-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: OAuth Consent
|
||||
description: OAuth 2.1 consent block for React Router
|
||||
preview: |-
|
||||
<BlockOverview name="oauth-consent-react-router" showFiles>
|
||||
<BlockPreview name="oauth-consent" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="oauth-consent" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="oauth-consent-react-router"
|
||||
description="OAuth 2.1 consent screen and the /oauth/consent route"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="oauth-consent-react-router" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="oauth-consent-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Password-based Authentication
|
||||
description: Password-based authentication block for React Router
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-react-router" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="password-based-auth-react-router"
|
||||
description="All needed components for the password based auth flow"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="password-based-auth-react-router" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your React Router project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Avatar Stack
|
||||
description: Avatar stack in realtime
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-avatar-stack-react-router" showFiles>
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-avatar-stack-react-router"
|
||||
description="Renders a stack of avatars which are connected via Supabase Realtime"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-avatar-stack-react-router" />
|
||||
<BlockItem name="realtime-avatar-stack-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Chat
|
||||
description: Real-time chat component for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-chat-react-router" showFiles>
|
||||
<DualRealtimeChat />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeChat />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-chat-react-router"
|
||||
description="Renders a real-time chat interface for users in a shared room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-chat-react-router" />
|
||||
<BlockItem name="realtime-chat-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Realtime Cursor
|
||||
description: Real-time cursor sharing for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-cursor-react-router" showFiles>
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-cursor-react-router"
|
||||
description="Renders realtime cursors from other users in a room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-cursor-react-router" />
|
||||
<BlockItem name="realtime-cursor-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Flow
|
||||
description: Real-time flow diagram editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-flow-react-router" showFiles>
|
||||
<DualRealtimeFlow />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeFlow />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-flow-react-router"
|
||||
description="Real-time flow diagram editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-flow-react-router" />
|
||||
<BlockItem name="realtime-flow-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
type Node,
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
type Edge,
|
||||
type Node,
|
||||
} from '@xyflow/react'
|
||||
|
||||
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Monaco
|
||||
description: Real-time Monaco editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-monaco-react-router" showFiles>
|
||||
<DualRealtimeMonaco />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeMonaco />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-monaco-react-router"
|
||||
description="Real-time Monaco editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-monaco-react-router" />
|
||||
<BlockItem name="realtime-monaco-react-router" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for React Router
|
||||
preview: |-
|
||||
<BlockOverview name="social-auth-react-router" showFiles>
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using GitHub provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
@@ -12,17 +14,10 @@ description: Social authentication block for React Router
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="social-auth-react-router"
|
||||
description="All needed components for the social auth flow"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="social-auth-react-router" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="social-auth-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your React Router project, you'll get all the necessary pages and components to set up a social authentication flow.
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
title: Supabase Client Libraries
|
||||
description: Supabase client for React Single Page Applications
|
||||
preview: <BlockOverview name="supabase-client-react" showFiles />
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="supabase-client-react" description="Supabase Client for React SPA" />
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-react" />
|
||||
<BlockItem name="supabase-client-react" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
---
|
||||
title: Current User Avatar
|
||||
description: Supabase Auth-aware avatar
|
||||
preview: |-
|
||||
<BlockOverview name="current-user-avatar-react" showFiles>
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="current-user-avatar-react" description="Renders the avatar of the current user." />
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="current-user-avatar-react" />
|
||||
<BlockItem name="current-user-avatar-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
---
|
||||
title: Dropzone (File Upload)
|
||||
description: Displays a control for easier uploading of files directly to Supabase Storage
|
||||
preview: |-
|
||||
<BlockOverview name="dropzone-react" showFiles>
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
|
||||
showCode={false}
|
||||
/>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
showCode={false}
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="dropzone-react"
|
||||
description="Displays a control for easier uploading of files directly to Supabase Storage"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="dropzone-react" />
|
||||
<BlockItem name="dropzone-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Infinite Query Hook
|
||||
description: React hook for infinite lists, fetching data from Supabase.
|
||||
description: 'React hook for infinite lists, fetching data from Supabase.'
|
||||
preview: |-
|
||||
<BlockOverview name="infinite-query-hook" showFiles>
|
||||
<BlockPreview name="infinite-list-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="infinite-list-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="infinite-query-hook"
|
||||
description="Installs the Infinite List component and necessary Supabase client setup."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="infinite-query-hook" />
|
||||
<BlockItem name="infinite-query-hook" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -23,7 +18,7 @@ The hook is fully typed, provided you have generated and setup your database typ
|
||||
|
||||
## Adding types
|
||||
|
||||
Before using this hook, we **highly** recommend you setup database types in your project. This will make the hook fully-typesafe. More info about generating Typescript types from database schema [here](https://supabase.com/docs/guides/api/rest/generating-types)
|
||||
Before using this hook, we **highly** recommend you setup database types in your project. This will make the hook fully-typesafe. More info about generating TypeScript types from database schema [here](https://supabase.com/docs/guides/api/rest/generating-types).
|
||||
|
||||
## Props
|
||||
|
||||
@@ -285,7 +280,7 @@ export const InfiniteListDemo = () => {
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Generating Typescript types from the database](https://supabase.com/docs/reference/javascript/typescript-support)
|
||||
- [Generating TypeScript types from the database](https://supabase.com/docs/reference/javascript/typescript-support)
|
||||
- [Supabase Database API](https://supabase.com/docs/reference/javascript/select)
|
||||
- [Supabase Pagination](https://supabase.com/docs/reference/javascript/select#pagination)
|
||||
- [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: OAuth Consent
|
||||
description: OAuth 2.1 consent block for React single-page applications
|
||||
preview: |-
|
||||
<BlockOverview name="oauth-consent-react" showFiles>
|
||||
<BlockPreview name="oauth-consent" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="oauth-consent" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="oauth-consent-react"
|
||||
description="OAuth 2.1 consent component for an existing authenticated application"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="oauth-consent-react" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="oauth-consent-react" />
|
||||
|
||||
## Usage
|
||||
|
||||
This block provides an OAuth 2.1 consent component for an app that already has authentication. It does not install sign-in, sign-up, callback, or router-specific routes.
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Password-based Authentication
|
||||
description: Password-based authentication block for React Single Page Applications
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-react" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="password-based-auth-react"
|
||||
description="All needed components for the password based auth flow"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="password-based-auth-react" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-react" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your React project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Avatar Stack
|
||||
description: Avatar stack in realtime
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-avatar-stack-react" showFiles>
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-avatar-stack-react"
|
||||
description="Renders a stack of avatars which are connected via Supabase Realtime"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-avatar-stack-react" />
|
||||
<BlockItem name="realtime-avatar-stack-react" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Chat
|
||||
description: Real-time chat component for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-chat-react" showFiles>
|
||||
<DualRealtimeChat />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeChat />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-chat-react"
|
||||
description="Renders a real-time chat interface for users in a shared room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-chat-react" />
|
||||
<BlockItem name="realtime-chat-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Realtime Cursor
|
||||
description: Real-time cursor sharing for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-cursor-react" showFiles>
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-cursor-react"
|
||||
description="Renders realtime cursors from other users in a room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-cursor-react" />
|
||||
<BlockItem name="realtime-cursor-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Flow
|
||||
description: Real-time flow diagram editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-flow-react" showFiles>
|
||||
<DualRealtimeFlow />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeFlow />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-flow-react"
|
||||
description="Real-time flow diagram editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-flow-react" />
|
||||
<BlockItem name="realtime-flow-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
type Node,
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
type Edge,
|
||||
type Node,
|
||||
} from '@xyflow/react'
|
||||
|
||||
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Monaco
|
||||
description: Real-time Monaco editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-monaco-react" showFiles>
|
||||
<DualRealtimeMonaco />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeMonaco />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-monaco-react"
|
||||
description="Real-time Monaco editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-monaco-react" />
|
||||
<BlockItem name="realtime-monaco-react" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for React Single Page Applications
|
||||
preview: |-
|
||||
<BlockOverview name="social-auth-react" showFiles>
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using GitHub provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
@@ -12,14 +14,10 @@ description: Social authentication block for React Single Page Applications
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="social-auth-react" description="All needed components for the social auth flow" />
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="social-auth-react" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="social-auth-react" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your React project, you'll get all the necessary pages and components to set up a social authentication flow.
|
||||
|
||||
@@ -3,6 +3,12 @@ title: AI Chat App
|
||||
description: >-
|
||||
A Next.js chat app with streaming responses, authentication, and saved
|
||||
conversations
|
||||
preview: |-
|
||||
<BlockOverview name="ai-chat-app">
|
||||
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
|
||||
<CatalogPreview kind="chat" />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
The [Supabase Community AI chatbot template](https://github.com/supabase-community/vercel-ai-chatbot) includes a chat interface, streaming responses, Supabase Auth, and conversation history in Postgres. Creating the app installs those features together.
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
---
|
||||
title: Flutter Starter
|
||||
description: 'A Flutter user management app with magic links, profiles, and avatar uploads'
|
||||
preview: |-
|
||||
<BlockOverview name="flutter-starter">
|
||||
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
|
||||
<CatalogPreview kind="avatar" />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
Start from the [Supabase Flutter user management example](https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management). It includes a Flutter app, the Supabase Flutter SDK, account screens, and local database migrations.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
---
|
||||
title: Next.js Starter
|
||||
description: 'A Next.js app with Supabase authentication, TypeScript, and Tailwind CSS'
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-nextjs" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
Start with a Next.js App Router project, then install the Library's password-based authentication block. The block adds sign-in, sign-up, password recovery, and a protected page.
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
---
|
||||
title: SaaS Starter
|
||||
description: A Next.js subscription app with Supabase Auth and Stripe billing
|
||||
preview: |-
|
||||
<BlockOverview name="saas-starter">
|
||||
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
|
||||
<CatalogPreview kind="dashboard" />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
The [Supabase Community subscription template](https://github.com/supabase-community/nextjs-subscription-payments) includes authentication, subscription checkout, a customer portal, and database migrations. Its components and billing routes are installed together when you create the app.
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
---
|
||||
title: Supabase Client Libraries
|
||||
description: Supabase client for TanStack Start
|
||||
preview: <BlockOverview name="supabase-client-tanstack" showFiles />
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="supabase-client-tanstack" description="Supabase Client for TanStack Start" />
|
||||
<BlockItem name="supabase-client-tanstack" />
|
||||
|
||||
<TanStackBeta />
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-tanstack" />
|
||||
|
||||
## Usage
|
||||
|
||||
This block installs Supabase clients for connecting your TanStack Start project to Supabase. They're designed for use in both server-side loaders and actions, as well as client-side components.
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Current User Avatar
|
||||
description: Supabase Auth-aware avatar
|
||||
preview: |-
|
||||
<BlockOverview name="current-user-avatar-tanstack" showFiles>
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="current-user-avatar-tanstack"
|
||||
description="Renders the avatar of the current user."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="current-user-avatar-tanstack" />
|
||||
<BlockItem name="current-user-avatar-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
---
|
||||
title: Dropzone (File Upload)
|
||||
description: Displays a control for easier uploading of files directly to Supabase Storage
|
||||
preview: |-
|
||||
<BlockOverview name="dropzone-tanstack" showFiles>
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
|
||||
showCode={false}
|
||||
/>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="dropzone-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
showCode={false}
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="dropzone-tanstack"
|
||||
description="Displays a control for easier uploading of files directly to Supabase Storage"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="dropzone-tanstack" />
|
||||
<BlockItem name="dropzone-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
---
|
||||
title: Headless App
|
||||
description: A backend with an agent as the primary interface
|
||||
preview: |-
|
||||
<BlockOverview name="headless-app-tanstack" showFiles>
|
||||
<BlockPreview name="headless-app" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
A headless app uses an AI agent as its primary interface. Users sign in and
|
||||
@@ -8,8 +12,6 @@ approve access in the browser, then work through their agent using MCP tools.
|
||||
This starter includes task tools and a database with Row Level Security (RLS)
|
||||
that limits each user to their own data.
|
||||
|
||||
<BlockPreview name="headless-app" />
|
||||
|
||||
## Installation
|
||||
|
||||
Create a TanStack Start app:
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: OAuth Consent
|
||||
description: OAuth 2.1 consent block for TanStack Start
|
||||
preview: |-
|
||||
<BlockOverview name="oauth-consent-tanstack" showFiles>
|
||||
<BlockPreview name="oauth-consent" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="oauth-consent" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="oauth-consent-tanstack"
|
||||
description="OAuth 2.1 consent screen and the /oauth/consent route"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="oauth-consent-tanstack" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/tanstack/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="oauth-consent-tanstack" />
|
||||
|
||||
## Usage
|
||||
|
||||
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
---
|
||||
title: Password-based Authentication
|
||||
description: Password-based authentication block for TanStack Start
|
||||
preview: |-
|
||||
<BlockOverview name="password-based-auth-tanstack" showFiles>
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="password-based-auth-tanstack" description="Supabase Client for TanStack Start" />
|
||||
|
||||
## Folder structure
|
||||
<BlockItem name="password-based-auth-tanstack" />
|
||||
|
||||
This block includes the [Supabase client](/library/docs/tanstack/client). If you already have one installed, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-tanstack" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your TanStack Start project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Avatar Stack
|
||||
description: Avatar stack in realtime
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-avatar-stack-tanstack" showFiles>
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<BlockPreview name="realtime-avatar-stack-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-avatar-stack-tanstack"
|
||||
description="Renders a stack of avatars which are connected via Supabase Realtime"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-avatar-stack-tanstack" />
|
||||
<BlockItem name="realtime-avatar-stack-tanstack" />
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Chat
|
||||
description: Real-time chat component for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-chat-tanstack" showFiles>
|
||||
<DualRealtimeChat />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeChat />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-chat-tanstack"
|
||||
description="Renders a real-time chat interface for users in a shared room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-chat-tanstack" />
|
||||
<BlockItem name="realtime-chat-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
---
|
||||
title: Realtime Cursor
|
||||
description: Real-time cursor sharing for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-cursor-tanstack" showFiles>
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<div className="flex flex-row -space-x-px w-full">
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
<BlockPreview name="realtime-cursor-demo" isPair />
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-cursor-tanstack"
|
||||
description="Renders realtime cursors from other users in a room."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-cursor-tanstack" />
|
||||
<BlockItem name="realtime-cursor-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Flow
|
||||
description: Real-time flow diagram editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-flow-tanstack" showFiles>
|
||||
<DualRealtimeFlow />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeFlow />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-flow-tanstack"
|
||||
description="Real-time flow diagram editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-flow-tanstack" />
|
||||
<BlockItem name="realtime-flow-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
type Node,
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
type Edge,
|
||||
type Node,
|
||||
} from '@xyflow/react'
|
||||
|
||||
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
---
|
||||
title: Realtime Monaco
|
||||
description: Real-time Monaco editor for collaborative applications
|
||||
preview: |-
|
||||
<BlockOverview name="realtime-monaco-tanstack" showFiles>
|
||||
<DualRealtimeMonaco />
|
||||
</BlockOverview>
|
||||
---
|
||||
|
||||
<DualRealtimeMonaco />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="realtime-monaco-tanstack"
|
||||
description="Real-time Monaco editor for collaborative applications."
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="realtime-monaco-tanstack" />
|
||||
<BlockItem name="realtime-monaco-tanstack" />
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user