Feat/design system site (#26586)

* init site

* add nav menu

* add props table component

* init all the examples

• also includes moving a bunch of ./ui files to ./ui-patterns

* update package

• might need to remove some of these

* Update rehype-component.ts

* move nav

* more changes

* add new source information to contentlayer

* fix copy buttons

* add text-confirm. start new concept of "fragments"

* move base path. add homepage. add theme selection

* added colors

* add form-item-layout

* temporary fix code blocks in light mode. they currently don't switch theme to theme

* start adding code themes

* Update dark.json

* update code block themes

* fix animations

* add cdmk search

* export registry of icons in ./icons package

* add comments

* add icon copy stuff

* Update icons.tsx

* more docs

* more docs. cleaned up colors and icons docs

* Update theming.mdx

* add more docs

* update

* add package

* Update tailwind.config.js

* update content

* Update drawer-demo.tsx

* Update aspect-ratio-demo.tsx

* add new blocks

* Update source-panel.tsx

* Update source-panel.tsx

* Update source-panel.tsx

* add new source block

* Update source-panel.tsx

* Update _app.tsx

* Update page.tsx

* Delete layout-old.tsx

* remove old themes

* remove old ui registry

* comment out nav items

* Update package-lock.json

* Update AssistantChatForm.tsx

* move back

* move again

* update

* Update index.tsx

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* update package

* udpate prettier

* fix tag issues

* Update package-lock.json

* update to contentlayer2

* update package-lock.json

* fix type errors

* ignore spelling

* Update avoid-typos.yml

* Update avoid-typos.yml

* Update package-lock.json

* Use ui type definitions.

* move tsconfig base stuff. fix content layer issue

* Update package-lock.json

* Update package-lock.json

---------

Co-authored-by: Alaister Young <a@alaisteryoung.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Jonathan Summers-Muir
2024-05-23 17:39:56 +08:00
committed by GitHub
parent cd43912a2f
commit b8243e06d8
313 changed files with 27023 additions and 355 deletions

View File

@@ -16,3 +16,4 @@ apps/docs/pages/guides/integrations/*.mdx
apps/studio/public
apps/**/.turbo
apps/docs/CONTRIBUTING.md
apps/design-system/__registry__

View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

38
apps/design-system/.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.contentlayer

View File

@@ -0,0 +1,36 @@
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).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [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.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
import { Mdx } from '@/components/mdx-components'
import { DocsPager } from '@/components/pager'
import { SourcePanel } from '@/components/source-panel'
import { DashboardTableOfContents } from '@/components/toc'
import { siteConfig } from '@/config/site'
import { getTableOfContents } from '@/lib/toc'
import { absoluteUrl, cn } from '@/lib/utils'
import '@/styles/code-block-variables.css'
import '@/styles/mdx.css'
import { allDocs } from 'contentlayer/generated'
import { ChevronRight } from 'lucide-react'
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import Balancer from 'react-wrap-balancer'
import { ScrollArea, Separator } from 'ui'
interface DocPageProps {
params: {
slug: string[]
}
}
async function getDocFromParams({ params }: DocPageProps) {
const slug = params.slug?.join('/') || ''
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
return null
}
return doc
}
export async function generateMetadata({ params }: DocPageProps): Promise<Metadata> {
const doc = await getDocFromParams({ params })
if (!doc) {
return {}
}
return {
title: doc.title,
description: doc.description,
openGraph: {
title: doc.title,
description: doc.description,
type: 'article',
url: absoluteUrl(doc.slug),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: 'summary_large_image',
title: doc.title,
description: doc.description,
images: [siteConfig.ogImage],
creator: '@shadcn',
},
}
}
export async function generateStaticParams(): Promise<DocPageProps['params'][]> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split('/'),
}))
}
export default async function DocPage({ params }: DocPageProps) {
const doc = await getDocFromParams({ params })
if (!doc) {
notFound()
}
const toc = await getTableOfContents(doc.body.raw)
return (
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] pr-6 lg:py-8">
<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="space-y-2 mb-5">
<h1 className={cn('scroll-m-20 text-4xl tracking-tight')}>{doc.title}</h1>
{doc.description && (
<p className="text-lg text-foreground-light">
<Balancer>{doc.description}</Balancer>
</p>
)}
</div>
<Separator className="mb-6" />
<SourcePanel doc={doc} />
<div className="pb-12">
<Mdx code={doc.body.code} />
</div>
<DocsPager doc={doc} />
</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>
</div>
)}
</main>
)
}

View File

@@ -0,0 +1,35 @@
import SideNavigation from '@/components/side-navigation'
import { SiteFooter } from '@/components/site-footer'
// import ThemeSettings from '@/components/theme-settings'
import TopNavigation from '@/components/top-navigation'
import { ScrollArea } from 'ui'
interface AppLayoutProps {
children: React.ReactNode
}
export default function AppLayout({ children }: AppLayoutProps) {
return (
<>
<TopNavigation />
{/* main container */}
<div className="">
{/* main content */}
<main className="flex-1 max-w-site mx-auto w-full border-l border-r">
{/* {children} */}
<div className="border-b">
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
<aside className="fixed top-10 z-30 hidden h-[calc(100vh-3rem)] w-full shrink-0 md:sticky md:block border-r">
<ScrollArea className="h-full py-6 lg:py-8">
<SideNavigation />
</ScrollArea>
</aside>
{children}
</div>
</div>
</main>
</div>
<SiteFooter />
</>
)
}

View File

@@ -0,0 +1,113 @@
import { DesignSystemMarks } from '@/components/design-system-marks'
import { HomepageSvgHandler } from '@/components/homepage-svg-handler'
import Image from 'next/image'
import { Separator } from 'ui'
export default function Home() {
return (
<main className="relative lg:gap-10 pr-6 lg:py-20">
<div className="mx-auto w-full min-w-0 max-w-6xl flex flex-col gap-20">
<div className="flex flex-col gap-8 justify-start">
{/* <div>
<DesignSystemMarks />
</div> */}
<div>
<h1 className="text-4xl text-foreground mb-3">Supabase Design System</h1>
<h2 className="text-xl text-foreground-light font-light">
Design resources for building consistent user experiences
</h2>
</div>
</div>
{/* Homepage items */}
<div className="grid grid-cols-2 gap-10">
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<HomepageSvgHandler name="atoms-illustration" />
<div>
<h3 className="font-medium text-foreground">Atom components</h3>
<p className="text-sm text-foreground-light">Building blocks of User interfaces</p>
</div>
</div>
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<HomepageSvgHandler name="fragments-illustration" />
<div>
<h3 className="font-medium text-foreground">Fragment components</h3>
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
</div>
</div>
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<div className="flex flex-col gap-1">
<div className="flex flex-row gap-0.5">
<div className="w-3 h-16 rounded border bg-foreground"></div>
<div className="w-3 h-16 rounded border bg-foreground-light"></div>
<div className="w-3 h-16 rounded border bg-foreground-lighter"></div>
<div className="w-3 h-16 rounded border bg-foreground-muted"></div>
<div className="w-3 h-16 rounded border"></div>
<div className="w-3 h-16 rounded border bg"></div>
<div className="w-3 h-16 rounded border bg-200"></div>
<div className="w-3 h-16 rounded border bg-surface-75"></div>
<div className="w-3 h-16 rounded border bg-surface-100"></div>
<div className="w-3 h-16 rounded border bg-surface-200"></div>
<div className="w-3 h-16 rounded border bg-surface-300"></div>
<div className="w-3 h-16 rounded border"></div>
<div className="w-3 h-16 rounded border bg-brand-200"></div>
<div className="w-3 h-16 rounded border bg-brand-300"></div>
<div className="w-3 h-16 rounded border bg-brand-400"></div>
<div className="w-3 h-16 rounded border bg-brand-500"></div>
<div className="w-3 h-16 rounded border bg-brand"></div>
<div className="w-3 h-16 rounded border bg-brand-600"></div>
<div className="w-3 h-16 rounded border"></div>
<div className="w-3 h-16 rounded border bg-warning-200"></div>
<div className="w-3 h-16 rounded border bg-warning-300"></div>
<div className="w-3 h-16 rounded border bg-warning-400"></div>
<div className="w-3 h-16 rounded border bg-warning-500"></div>
<div className="w-3 h-16 rounded border bg-warning"></div>
<div className="w-3 h-16 rounded border bg-warning-600"></div>
<div className="w-3 h-16 rounded border"></div>
<div className="w-3 h-16 rounded border bg-destructive-200"></div>
<div className="w-3 h-16 rounded border bg-destructive-300"></div>
<div className="w-3 h-16 rounded border bg-destructive-400"></div>
<div className="w-3 h-16 rounded border bg-destructive-500"></div>
<div className="w-3 h-16 rounded border bg-destructive"></div>
<div className="w-3 h-16 rounded border bg-destructive-600"></div>
<div className="w-3 h-16 rounded border"></div>
</div>
</div>
<div>
<h3 className="font-medium text-foreground">Colors</h3>
<p className="text-sm text-foreground-light">Building blocks of User interfaces</p>
</div>
</div>
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<HomepageSvgHandler name="fragments-illustration" />
<div>
<h3 className="font-medium text-foreground">Theming</h3>
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
</div>
</div>
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<HomepageSvgHandler name="atoms-illustration" />
<div>
<h3 className="font-medium text-foreground">Background / Surfaces</h3>
<p className="text-sm text-foreground-light">Building blocks of User interfaces</p>
</div>
</div>
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
<HomepageSvgHandler name="fragments-illustration" />
<div>
<h3 className="font-medium text-foreground">Icons</h3>
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
</div>
</div>
</div>
</div>
</main>
)
}

View File

@@ -0,0 +1,18 @@
'use client'
import * as React from 'react'
import { Provider as JotaiProvider } from 'jotai'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { ThemeProviderProps } from 'next-themes/dist/types'
import { TooltipProvider_Shadcn_ } from 'ui'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<JotaiProvider>
<NextThemesProvider {...props}>
<TooltipProvider_Shadcn_ delayDuration={0}>{children}</TooltipProvider_Shadcn_>
</NextThemesProvider>
</JotaiProvider>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,29 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* :root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
} */
body {
/* color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb)))
rgb(var(--background-start-rgb)); */
}
/*
@layer utilities {
.text-balance {
text-wrap: balance;
}
} */

View File

@@ -0,0 +1,38 @@
import '@/styles/globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { ThemeProvider } from './Providers'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
interface RootLayoutProps {
children: React.ReactNode
}
export default async function Layout({ children }: RootLayoutProps) {
// console.log('Root Layout mounted or re-rendered')
return (
<html
// className="dark"
lang="en"
// data-theme="dark"
// style={{ colorScheme: 'dark' }}
suppressHydrationWarning
>
<head />
<body className={inter.className}>
<ThemeProvider themes={['dark', 'light', 'deep-dark']} defaultTheme="system" enableSystem>
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
</div>
</ThemeProvider>
</body>
</html>
)
}

View File

@@ -0,0 +1,17 @@
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_ } from 'ui'
interface CalloutProps {
icon?: string
title?: string
children?: React.ReactNode
}
export function Callout({ title, children, icon, ...props }: CalloutProps) {
return (
<Alert_Shadcn_ {...props}>
{icon && <span className="mr-4 text-2xl">{icon}</span>}
{title && <AlertTitle_Shadcn_>{title}</AlertTitle_Shadcn_>}
<AlertDescription_Shadcn_>{children}</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)
}

View File

@@ -0,0 +1,18 @@
import * as React from 'react'
const ClassLabel = React.forwardRef<HTMLSpanElement, { children: React.ReactNode }>(
({ children }, ref) => {
return (
<span
ref={ref}
className="bg-surface-100 rounded-full border px-2 font-mono text-xs text-foreground-lighter group-data-[state=open]:text-foreground"
>
{children}
</span>
)
}
)
ClassLabel.displayName = 'ClassLabel'
export { ClassLabel }

View File

@@ -0,0 +1,16 @@
'use client'
import { useState } from 'react'
export function ClickCounter() {
const [count, setCount] = useState(0)
return (
<button
onClick={() => setCount(count + 1)}
className="whitespace-nowrap rounded-lg bg-gray-700 px-3 py-1 text-sm font-medium tabular-nums text-gray-100 hover:bg-gray-500 hover:text-white"
>
{count} Clicks
</button>
)
}

View File

@@ -0,0 +1,53 @@
'use client'
import * as React from 'react'
import { cn } from 'ui'
import { Button } from 'ui'
import {
Collapsible_Shadcn_ as Collapsible,
CollapsibleContent_Shadcn_ as CollapsibleContent,
CollapsibleTrigger_Shadcn_ as CollapsibleTrigger,
} from 'ui'
interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
expandButtonTitle?: string
}
export function CodeBlockWrapper({
expandButtonTitle = 'View Code',
className,
children,
...props
}: CodeBlockProps) {
const [isOpened, setIsOpened] = React.useState(false)
return (
<Collapsible open={isOpened} onOpenChange={setIsOpened}>
<div className={cn('relative overflow-hidden', className)} {...props}>
<CollapsibleContent forceMount className={cn('overflow-hidden', !isOpened && 'max-h-32')}>
<div
className={cn(
'[&_pre]:my-0 [&_pre]:max-h-[650px] [&_pre]:pb-[100px]',
!isOpened ? '[&_pre]:overflow-hidden' : '[&_pre]:overflow-auto]'
)}
>
{children}
</div>
</CollapsibleContent>
<div
className={cn(
'absolute flex items-center justify-center bg-gradient-to-b from-zinc-700/30 to-zinc-950/90 p-2',
isOpened ? 'inset-x-0 bottom-0 h-12' : 'inset-0'
)}
>
<CollapsibleTrigger asChild>
<Button type="secondary" className="h-8 text-xs">
{isOpened ? 'Collapse' : expandButtonTitle}
</Button>
</CollapsibleTrigger>
</div>
</div>
</Collapsible>
)
}

View File

@@ -0,0 +1,107 @@
'use client'
import { Index } from '@/__registry__'
import * as React from 'react'
import { useConfig } from '@/hooks/use-config'
import { cn } from 'ui'
import { styles } from '@/registry/styles'
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
name: string
extractClassname?: boolean
extractedClassNames?: string
align?: 'center' | 'start' | 'end'
peekCode?: boolean
showGrid?: boolean
showDottedGrid?: boolean
wide?: boolean
}
export function CodeFragment({
name,
children,
className,
extractClassname,
extractedClassNames,
align = 'center',
peekCode = false,
showGrid = false,
showDottedGrid = true,
wide = false,
...props
}: ComponentPreviewProps) {
const [config] = useConfig()
const index = styles.findIndex((style) => style.name === config.style)
const Codes = React.Children.toArray(children) as React.ReactElement[]
const Code = Codes[index]
const [expand, setExpandState] = React.useState(false)
const Preview = React.useMemo(() => {
// console.log('Index', Index)
// console.log('name', name)
// console.log('config.style', config.style)
const Component = Index[config.style][name]?.component
// const Component = Index[name]?.component
if (!Component) {
return (
<p className="text-sm text-muted-foreground">
Code fragment{' '}
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
{name}
</code>{' '}
not found in registry.
</p>
)
}
return <Component />
}, [name, config.style])
const ComponentPreview = React.useMemo(() => {
return (
<>
<div
className={cn('preview flex min-h-[350px] w-full justify-center p-10', {
'items-center': align === 'center',
'items-start': align === 'start',
'items-end': align === 'end',
})}
>
<React.Suspense
fallback={
<div className="flex items-center text-sm text-muted-foreground">Loading...</div>
}
>
{Preview}
</React.Suspense>
</div>
</>
)
}, [Preview, align])
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-md border-t border-l border-r border-b bg-studio overflow-hidden'
)}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
)}
{showDottedGrid && (
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.02)_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
)}
<div className="z-10 relative">{ComponentPreview}</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,155 @@
import { cn } from 'ui/src/lib/utils/cn'
// import { Card } from '@ui/components/shadcn/ui/card'
import color from 'ui/src/lib/tailwind-demo-classes'
import { Grid, GridItem } from './grid'
const Colors = ({
definition,
}: {
definition: 'background' | 'border' | 'text' | 'colors' | 'palletes'
}) => {
const Example = ({ x }: { x: string }) => {
switch (definition) {
case 'background':
return (
<div className={cn(x, 'relative w-full h-12 border border-overlay rounded-full')}></div>
)
break
case 'border':
return <div className={cn(x, 'relative w-full h-12 border-4 rounded-full')}></div>
break
case 'text':
return (
<span className={cn(x, 'relative w-full h-12 flex items-center justify-center')}>
Postgres
</span>
)
break
case 'colors':
return (
<div className={cn(x, 'relative w-full h-12 border border-overlay rounded-full')}></div>
)
break
case 'palletes':
return (
<div className={cn(x, 'relative w-full h-12 border border-overlay rounded-full')}></div>
)
break
default:
break
}
}
return (
<>
<Grid>
{color[definition].map((x: string, i) => {
return (
<GridItem
key={i}
className={cn(x.includes('contrast') && 'bg-foreground hover:bg-foreground-light')}
>
<Example x={x} />
<span className="bg-surface-100 rounded-full border px-2 font-mono text-xs text-foreground-lighter group-data-[state=open]:text-foreground">
{x}
</span>
</GridItem>
)
})}
</Grid>
</>
)
}
{
/* <div className="flex flex-col gap-12 py-20">
<div className="flex flex-row gap-12">
<div>
<h5 className="mb-3">Background</h5>
<div className="flex flex-col gap-3">
{color.background.map((x: string, i) => {
return (
<div className="flex gap-3 items-center" key={i}>
<div className={cn(x, 'relative w-12 h-12 border border-overlay shadow')}></div>
<div className="font-mono text-sm bg-surface-100 px-2 py-0.5 rounded-full">
{x}
</div>
</div>
)
})}
</div>
</div>
<div>
<h5 className="mb-3">Border</h5>
<div className="flex flex-col gap-3">
{color.border.map((x, i) => {
return (
<div className="flex gap-3 items-center" key={i}>
<div className={cn(x, 'relative w-12 h-12 border-4')}></div>
<div className="font-mono text-sm bg-surface-100 px-2 py-0.5 rounded-full">
{x}
</div>
</div>
)
})}
</div>
</div>
<div>
<h5 className="mb-3">texts</h5>
<div className="flex flex-col gap-3">
{color.text.map((x, i) => {
return (
<div className="flex gap-3 items-center" key={i}>
<span className={cn(x, 'relative w-12 h-12 flex items-center justify-center')}>
###
</span>
<div className="font-mono text-sm bg-surface-100 px-2 py-0.5 rounded-full">
{x}
</div>
</div>
)
})}
</div>
</div>
</div>
<div className="flex flex-row gap-12">
<div>
<h5 className="mb-3">Colors</h5>
<div className="flex flex-col gap-3">
{color.colors.map((x: string, i) => {
return (
<div className="flex gap-3 items-center" key={i}>
<div className={cn(x, 'relative w-12 h-12 border border-overlay shadow')}></div>
<div className="font-mono text-sm bg-surface-100 px-2 py-0.5 rounded-full">
{x}
</div>
</div>
)
})}
</div>
</div>
<div>
<h5 className="mb-3">Palletes</h5>
<div className="flex flex-col gap-3">
{color.palletes.map((x, i) => {
return (
<div className="flex gap-3 items-center" key={i}>
<div className={cn(x, 'relative w-12 h-12 border border-overlay shadow')}></div>
<div className="font-mono text-sm bg-surface-100 px-2 py-0.5 rounded-full">
{x}
</div>
</div>
)
})}
</div>
</div>
</div>
</div> */
}
export { Colors }

View File

@@ -0,0 +1,132 @@
'use client'
import * as React from 'react'
import { useRouter } from 'next/navigation'
import { DialogProps } from '@radix-ui/react-alert-dialog'
import { CircleIcon, FileIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
import { useTheme } from 'next-themes'
import { docsConfig } from '@/config/docs'
import { cn } from '@/lib/utils'
import { Button } from 'ui'
import {
CommandDialog,
CommandEmpty_Shadcn_,
CommandGroup_Shadcn_,
CommandInput_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
CommandSeparator_Shadcn_,
} from 'ui'
export function CommandMenu({ ...props }: DialogProps) {
const router = useRouter()
const [open, setOpen] = React.useState(false)
const { setTheme } = useTheme()
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if ((e.key === 'k' && (e.metaKey || e.ctrlKey)) || e.key === '/') {
if (
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement
) {
return
}
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [])
const runCommand = React.useCallback((command: () => unknown) => {
setOpen(false)
command()
}, [])
return (
<>
<Button
type="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 md:w-40 lg:w-64
hover:border-foreground-muted hover:bg-surface-100 hover:text-foreground-lighter
`
)}
onClick={() => setOpen(true)}
{...props}
>
<span className="hidden lg:inline-flex">Search Design System...</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 border bg-surface-200 px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex text-foreground-light">
<span className="text-sm"></span>K
</kbd>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput_Shadcn_ placeholder="Type a command or search..." />
<CommandList_Shadcn_>
<CommandEmpty_Shadcn_>No results found.</CommandEmpty_Shadcn_>
{/* <CommandGroup_Shadcn_ heading="Links">
{docsConfig.mainNav
.filter((navitem) => !navitem.external)
.map((navItem) => (
<CommandItem_Shadcn_
key={navItem.href}
value={navItem.title}
onSelect={() => {
runCommand(() => router.push(navItem.href as string))
}}
>
<FileIcon className="mr-2 h-4 w-4" strokeWidth={1} />
{navItem.title}
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_> */}
{docsConfig.sidebarNav.map((group) => (
<CommandGroup_Shadcn_ key={group.title} heading={group.title}>
{group.items.map((navItem) => (
<CommandItem_Shadcn_
key={navItem.href}
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.title}
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_>
))}
<CommandSeparator_Shadcn_ />
<CommandGroup_Shadcn_ heading="Theme">
<CommandItem_Shadcn_ onSelect={() => runCommand(() => setTheme('light'))}>
<SunIcon className="mr-2 h-4 w-4" strokeWidth={1} />
Light
</CommandItem_Shadcn_>
<CommandItem_Shadcn_ onSelect={() => runCommand(() => setTheme('dark'))}>
<MoonIcon className="mr-2 h-4 w-4" strokeWidth={1} />
Dark
</CommandItem_Shadcn_>
<CommandItem_Shadcn_ onSelect={() => runCommand(() => setTheme('deep-dark'))}>
<MoonIcon className="mr-2 h-4 w-4" strokeWidth={1} />
Deep Dark
</CommandItem_Shadcn_>
<CommandItem_Shadcn_ onSelect={() => runCommand(() => setTheme('system'))}>
<LaptopIcon className="mr-2 h-4 w-4" strokeWidth={1} />
System
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
</CommandList_Shadcn_>
</CommandDialog>
</>
)
}

View File

@@ -0,0 +1,97 @@
'use client'
import * as React from 'react'
import { cn } from 'ui'
import {
// CopyButton,
CopyWithClassNames,
} from '@/components/copy-button'
import {
Tabs_Shadcn_ as Tabs,
TabsContent_Shadcn_ as TabsContent,
TabsList_Shadcn_ as TabsList,
TabsTrigger_Shadcn_ as TabsTrigger,
} from 'ui'
interface ComponentExampleProps extends React.HTMLAttributes<HTMLDivElement> {
extractClassname?: boolean
extractedClassNames?: string
align?: 'center' | 'start' | 'end'
src?: string
}
export function ComponentExample({
children,
className,
extractClassname,
extractedClassNames,
align = 'center',
src: _,
...props
}: ComponentExampleProps) {
const [Example, Code, ...Children] = React.Children.toArray(children) as React.ReactElement[]
const codeString = React.useMemo(() => {
if (typeof Code?.props['data-rehype-pretty-code-fragment'] !== 'undefined') {
const [, Button] = React.Children.toArray(Code.props.children) as React.ReactElement[]
return Button?.props?.value || Button?.props?.__rawString__ || null
}
}, [Code])
return (
<div className={cn('group relative my-4 flex flex-col gap-2', className)} {...props}>
<Tabs defaultValue="preview" className="relative mr-auto w-full">
<div className="flex items-center justify-between pb-3">
<TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="preview"
className="relative rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
className="relative rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Code
</TabsTrigger>
</TabsList>
{
extractedClassNames ? (
<CopyWithClassNames
value={codeString}
classNames={extractedClassNames}
className="absolute right-4 top-20"
/>
) : undefined
// codeString && <CopyButton value={codeString} className="absolute right-4 top-20" />
}
</div>
<TabsContent value="preview" className="rounded-md border">
<div
className={cn('flex min-h-[350px] justify-center p-10', {
'items-center': align === 'center',
'items-start': align === 'start',
'items-end': align === 'end',
})}
>
{Example}
</div>
</TabsContent>
<TabsContent value="code">
<div className="flex flex-col space-y-4">
<div className="w-full rounded-md [&_button]:hidden [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
{Code}
</div>
{Children?.length ? (
<div className="rounded-md [&_button]:hidden [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
{Children}
</div>
) : null}
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,260 @@
'use client'
import * as React from 'react'
import { Index } from '@/__registry__'
import {
Button,
CollapsibleContent_Shadcn_,
CollapsibleTrigger_Shadcn_,
Collapsible_Shadcn_,
cn,
} from 'ui'
import { useConfig } from '@/hooks/use-config'
import {
// CopyButton,
CopyWithClassNames,
} from '@/components/copy-button'
// import { Icons } from '@/components/icons'
// import { StyleSwitcher } from '@/components/style-switcher'
// import { ThemeWrapper } from '@/components/theme-wrapper'
import {
Tabs_Shadcn_ as Tabs,
TabsContent_Shadcn_ as TabsContent,
TabsList_Shadcn_ as TabsList,
TabsTrigger_Shadcn_ as TabsTrigger,
} from 'ui'
// import { LoaderCircle } from 'lucide-react'
import { styles } from '@/registry/styles'
import { ChevronRight, Expand } from 'lucide-react'
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
name: string
extractClassname?: boolean
extractedClassNames?: string
align?: 'center' | 'start' | 'end'
peekCode?: boolean
showGrid?: boolean
showDottedGrid?: boolean
wide?: boolean
}
export function ComponentPreview({
name,
children,
className,
extractClassname,
extractedClassNames,
align = 'center',
peekCode = false,
showGrid = false,
showDottedGrid = true,
wide = false,
...props
}: ComponentPreviewProps) {
const [config] = useConfig()
const index = styles.findIndex((style) => style.name === config.style)
const Codes = React.Children.toArray(children) as React.ReactElement[]
const Code = Codes[index]
const [expand, setExpandState] = React.useState(false)
const Preview = React.useMemo(() => {
// console.log('Index', Index)
// console.log('name', name)
// console.log('config.style', config.style)
const Component = Index[config.style][name]?.component
// const Component = Index[name]?.component
if (!Component) {
return (
<p className="text-sm text-muted-foreground">
Component{' '}
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
{name}
</code>{' '}
not found in registry.
</p>
)
}
return <Component />
}, [name, config.style])
const codeString = React.useMemo(() => {
if (typeof Code?.props['data-rehype-pretty-code-fragment'] !== 'undefined') {
const [, Button] = React.Children.toArray(Code.props.children) as React.ReactElement[]
return Button?.props?.value || Button?.props?.__rawString__ || null
}
}, [Code])
const ComponentPreview = React.useMemo(() => {
return (
<>
{/* <ThemeWrapper defaultTheme="zinc"> */}
<div
className={cn('preview flex min-h-[350px] w-full justify-center p-10', {
'items-center': align === 'center',
'items-start': align === 'start',
'items-end': align === 'end',
})}
>
<React.Suspense
fallback={
<div className="flex items-center text-sm text-muted-foreground">
{/* <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> */}
{/* <LoaderCircle className="mr-2 h-4 w-4 animate-spin" /> */}
Loading...
</div>
}
>
{Preview}
</React.Suspense>
</div>
{/* </ThemeWrapper> */}
</>
)
}, [Preview, align])
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
if (peekCode) {
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio overflow-hidden'
)}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
)}
{showDottedGrid && (
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.02)_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
)}
<div className="z-10 relative">{ComponentPreview}</div>
{/* <div className="preview-grid-background"></div> */}
</div>
<div className="flex flex-col space-y-4">
<div
className={cn(
'relative',
'w-full rounded-md [&_pre]:my-0',
expand
? '[&_pre]:overflow-auto'
: 'inset-0 [&_pre]:max-h-[196px] [&_pre]:overflow-hidden',
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none'
)}
>
{Code}
<div className="absolute bottom-0 w-full flex justify-center mb-4">
<Button
className="rounded-full"
onClick={() => setExpandState(!expand)}
type="default"
icon={<Expand className="text-foreground-lighter" />}
>
{expand ? 'Collapse code' : 'Expand code'}
</Button>
</div>
</div>
</div>
</div>
)
}
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio overflow-hidden'
)}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
)}
{showDottedGrid && (
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.02)_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
)}
<div className="z-10 relative">{ComponentPreview}</div>
{/* <div className="preview-grid-background"></div> */}
</div>
<Collapsible_Shadcn_>
<CollapsibleTrigger_Shadcn_
className={`
flex
gap-3 items-center
w-full
font-mono
text-xs
text-foreground-light
px-4 py-4
border border-r
group
data-[state=closed]:rounded-bl-md data-[state=closed]:rounded-br-md
`}
>
<ChevronRight
className="transition-all group-data-[state=open]:rotate-90 text-foreground-lighter"
size={14}
/>
View code
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_ className="transition-all">
<div
className={cn(
'relative',
'w-full rounded-md [&_pre]:my-0',
'[&_pre]:overflow-auto',
'[&_pre]:max-h-[320px]',
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none [&_pre]:border-t-transparent'
)}
>
{Code}
</div>
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
</div>
)
return (
<div
className={cn('group relative my-4 flex flex-col gap-2', wideClasses, className)}
{...props}
>
<Tabs defaultValue="preview" className="relative mr-auto w-full">
<div className="flex items-center justify-between pb-3">
<TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="preview"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Code
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="preview" className="relative rounded-md border bg-studio">
{ComponentPreview}
</TabsContent>
<TabsContent value="code">
<div className="flex flex-col space-y-4">
<div className="w-full rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
{Code}
</div>
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -0,0 +1,58 @@
import fs from 'fs'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
// import { parse } from 'react-docgen'
export function ComponentProps(props: any) {
// map through all props types for this component DropdownMenu
// return a table with the prop name, type, default value, and description
// const code = `
// /** My first component */
// export default ({ name }: { name: string }) => <div>{{name}}</div>;
// `
// const documentation = parse(code)
//
// console.log(documentation)
// console.log('from the component props', JSON.parse(props.docs))
const docs = JSON.parse(props.docs)
// console.log(docs.props)
return (
<div className="space-y-2">
<p className="font-medium text-foreground-light">{props.children}</p>
<Table>
<TableHeader>
<TableRow>
<TableHead className="font-mono uppercase text-xs font-normal">Prop Name</TableHead>
<TableHead className="font-mono uppercase text-xs font-normal">Required</TableHead>
<TableHead className="font-mono uppercase text-xs font-normal">Type</TableHead>
<TableHead className="font-mono uppercase text-xs text-right font-normal">
Description
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(docs.props).map(([propName, prop], index) => (
<TableRow key={index}>
<TableCell>{propName}</TableCell>
{/*
// @ts-ignore */}
<TableCell>{prop.required ? 'Yes' : 'No'}</TableCell>
{/*
// @ts-ignore */}
<TableCell>{prop.flowType.name}</TableCell>
{/*
// @ts-ignore */}
<TableCell className="text-right">{prop.description}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}

View File

@@ -0,0 +1,21 @@
'use client'
import * as React from 'react'
import { cn } from 'ui'
import { CodeBlockWrapper } from '@/components/code-block-wrapper'
interface ComponentSourceProps extends React.HTMLAttributes<HTMLDivElement> {
src: string
}
export function ComponentSource({ children, className, ...props }: ComponentSourceProps) {
return (
<CodeBlockWrapper
expandButtonTitle="Expand"
className={cn('my-6 overflow-hidden rounded-md', className)}
>
{children}
</CodeBlockWrapper>
)
}

View File

@@ -0,0 +1,179 @@
'use client'
import * as React from 'react'
import { DropdownMenuTriggerProps } from '@radix-ui/react-dropdown-menu'
// import { NpmCommands } from 'types/unist'
// import { Event, trackEvent } from '@/lib/events'
import { cn } from 'ui'
import { Button } from 'ui'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
import { Check, Copy } from 'lucide-react'
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
value: string
src?: string
// event?: Event['name']
}
export async function copyToClipboardWithMeta(
value: string
// event?: Event
) {
navigator.clipboard.writeText(value)
if (event) {
// trackEvent(event)
}
}
export function CopyButton({
value,
className,
src,
// event,
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
return (
<Button
size="small"
type="outline"
className={cn(
'relative z-10 h-6 w-6 text-foreground-muted hover:bg-surface-100 hover:text-foreground p-0',
className
)}
onClick={() => {
copyToClipboardWithMeta(
value
// event
// ? {
// name: event,
// properties: {
// code: value,
// },
// }
// : undefined
)
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <Check className="h-3 w-3 text-brand-600" /> : <Copy className="h-3 w-3" />}
</Button>
)
}
interface CopyWithClassNamesProps extends DropdownMenuTriggerProps {
value: string
classNames: string
className?: string
}
export function CopyWithClassNames({
value,
classNames,
className,
...props
}: CopyWithClassNamesProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
const copyToClipboard = React.useCallback((value: string) => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}, [])
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="small"
type="outline"
className={cn(
'relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50',
className
)}
>
{hasCopied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
<span className="sr-only">Copy</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyToClipboard(value)}>Component</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyToClipboard(classNames)}>Classname</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
interface CopyNpmCommandButtonProps extends DropdownMenuTriggerProps {
// commands: Required<NpmCommands>
}
// export function CopyNpmCommandButton({ commands, className, ...props }: CopyNpmCommandButtonProps) {
// const [hasCopied, setHasCopied] = React.useState(false)
// React.useEffect(() => {
// setTimeout(() => {
// setHasCopied(false)
// }, 2000)
// }, [hasCopied])
// // const copyCommand = React.useCallback((value: string, pm: 'npm' | 'pnpm' | 'yarn' | 'bun') => {
// // copyToClipboardWithMeta(value,
// // // {
// // // name: 'copy_npm_command',
// // // properties: {
// // // command: value,
// // // pm,
// // // },
// // })
// // setHasCopied(true)
// // }, [])
// return (
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button
// // size="icon"
// type="outline"
// className={cn(
// 'relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50',
// className
// )}
// >
// {hasCopied ? <CheckIcon className="h-3 w-3" /> : <CopyIcon className="h-3 w-3" />}
// <span className="sr-only">Copy</span>
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="end">
// <DropdownMenuItem onClick={() => copyCommand(commands.__npmCommand__, 'npm')}>
// npm
// </DropdownMenuItem>
// <DropdownMenuItem onClick={() => copyCommand(commands.__yarnCommand__, 'yarn')}>
// yarn
// </DropdownMenuItem>
// <DropdownMenuItem onClick={() => copyCommand(commands.__pnpmCommand__, 'pnpm')}>
// pnpm
// </DropdownMenuItem>
// <DropdownMenuItem onClick={() => copyCommand(commands.__bunCommand__, 'bun')}>
// bun
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// )
// }

View File

@@ -0,0 +1,19 @@
'use client'
import SVG from 'react-inlinesvg'
import { useTheme } from 'next-themes'
function DesignSystemMarks() {
const { resolvedTheme } = useTheme()
const isDark = resolvedTheme?.includes('dark')
return (
<SVG
className="h-4 w-auto"
src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/design-system-marks/design-system-marks--${isDark ? 'dark' : 'light'}.svg`}
/>
)
}
export { DesignSystemMarks }

View File

@@ -0,0 +1,15 @@
import * as React from 'react'
const ExampleLabel = React.forwardRef<HTMLSpanElement, { children: React.ReactNode }>(
({ children }, ref) => {
return (
<span ref={ref} className="text-xs flex gap-3 items-center text-foreground-muted">
{children}
</span>
)
}
)
ExampleLabel.displayName = 'ExampleLabel'
export { ExampleLabel }

View File

@@ -0,0 +1,62 @@
import { forwardRef } from 'react'
import { cn } from 'ui'
const Grid = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
({ children, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn(
'grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 border-t border-l my-12',
props.className
)}
>
{children}
</div>
)
}
)
Grid.displayName = 'Grid'
const GridItem = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
({ children, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn(
`
relative
min-h-32
flex gap-4 flex-col items-center p-4
border-b border-r
bg-surface-75/50
justify-center hover:bg-surface-100
group
cursor-pointer
`,
props.className
)}
>
<div
className="
absolute
w-full h-full box-content
transition
group-hover:border
group-hover:border-foreground-muted
group-data-[state=open]:border
group-data-[state=open]:border-foreground-muted
"
></div>
{children}
</div>
)
}
)
GridItem.displayName = 'GridItem'
export { Grid, GridItem }

View File

@@ -0,0 +1,19 @@
'use client'
import SVG from 'react-inlinesvg'
import { useTheme } from 'next-themes'
const HomepageSvgHandler = ({ name }: { name: string }) => {
const { resolvedTheme } = useTheme()
return (
<div>
<SVG
className="h-32 w-auto"
src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/design-system-marks/${name}--${resolvedTheme}.svg`}
/>
</div>
)
}
export { HomepageSvgHandler }

View File

@@ -0,0 +1,70 @@
import { Index } from 'icons/__registry__/index'
import { Copy } from 'lucide-react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
IconCopy,
} from 'ui'
import { Grid, GridItem } from './grid'
function Icons() {
return (
<>
<Grid>
{Index.map((icon: any, i: number) => (
<DropdownMenu key={i} modal={false}>
<DropdownMenuTrigger asChild>
<GridItem>
<icon.component
strokeWidth={1.5}
size={21}
className="group-data-[state=open]:scale-125 transition-all"
/>
<span className="bg-surface-100 rounded-full border px-2 font-mono text-xs text-foreground-lighter group-data-[state=open]:text-foreground">
{icon.name}
</span>
</GridItem>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48">
<DropdownMenuGroup>
<DropdownMenuItem
className="flex items-center gap-2"
onSelect={() => navigator.clipboard.writeText(icon.name)}
>
<Copy size={14} strokeWidth={1} />
Copy name
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center gap-2"
onSelect={() => navigator.clipboard.writeText(icon.jsx)}
>
<Copy size={14} strokeWidth={1} />
Copy JSX
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center gap-2"
onSelect={() => navigator.clipboard.writeText(icon.import)}
>
<Copy size={14} strokeWidth={1} />
Copy import path
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center gap-2"
onSelect={() => navigator.clipboard.writeText(icon.svg)}
>
<Copy size={14} strokeWidth={1} />
Copy SVG
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
))}
</Grid>
</>
)
}
export { Icons }

View File

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

View File

@@ -0,0 +1,72 @@
import Link from 'next/link'
import { Doc } from 'contentlayer/generated'
import { NavItem, NavItemWithChildren } from '@/types/nav'
import { docsConfig } from '@/config/docs'
import { cn } from '@/lib/utils'
import { buttonVariants } from 'ui'
import { ChevronLeft, ChevronRight } from 'lucide-react'
interface DocsPagerProps {
doc: Doc
}
export function DocsPager({ doc }: DocsPagerProps) {
const pager = getPagerForDoc(doc)
if (!pager) {
return null
}
return (
<div className="flex flex-row items-center justify-between">
{pager?.prev?.href && (
<Link
href={pager.prev.href}
className="flex gap-3 text-foreground-light hover:text-foreground group"
>
<ChevronLeft className="mr-2 h-4 w-4 self-end mb-1 text-foreground-muted group-hover:text-foreground-lighter" />
<div className="flex flex-col gap-1">
<span className="text-xs font-mono uppercase tracking-wider text-foreground-muted group-hover:text-foreground-lighter">
Previous
</span>
{pager.prev.title}
</div>
</Link>
)}
{pager?.next?.href && (
<Link
href={pager.next.href}
className="flex gap-3 text-foreground-light hover:text-foreground group"
>
<div className="flex flex-col gap-1">
<span className="text-xs font-mono uppercase tracking-wider text-foreground-muted group-hover:text-foreground-lighter">
Next
</span>
{pager.next.title}
</div>
<ChevronRight className="ml-2 h-4 w-4 self-end mb-1 text-foreground-muted group-hover:text-foreground-lighter" />
</Link>
)}
</div>
)
}
export function getPagerForDoc(doc: Doc) {
const flattenedLinks = [null, ...flatten(docsConfig.sidebarNav), null]
const activeIndex = flattenedLinks.findIndex((link) => doc.slug === link?.href)
const prev = activeIndex !== 0 ? flattenedLinks[activeIndex - 1] : null
const next = activeIndex !== flattenedLinks.length - 1 ? flattenedLinks[activeIndex + 1] : null
return {
prev,
next,
}
}
export function flatten(links: NavItemWithChildren[]): NavItem[] {
return links
.reduce<NavItem[]>((flat, link) => {
return flat.concat(link.items?.length ? flatten(link.items) : link)
}, [])
.filter((link) => !link?.disabled)
}

View File

@@ -0,0 +1,43 @@
'use client'
import { SidebarNavItem } from '@/types/nav'
import { cn } from 'ui/src/lib/utils/cn'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
import React from 'react'
const NavigationItem: React.FC<{ item: SidebarNavItem }> = React.memo(({ item }) => {
const pathname = usePathname()
const isActive = pathname === item.href
return (
<Link
href={`${item.href}`}
className={cn(
'relative',
'flex',
'items-center',
'h-8',
'text-foreground-light px-6',
!isActive && 'hover:bg-surface-100 hover:text-foreground',
isActive && 'bg-surface-200 text-foreground',
'transition-all'
)}
>
<div
className={cn(
'transition',
'absolute left-0 w-1 h-full bg-foreground',
isActive ? 'opacity-100' : 'opacity-0'
)}
></div>
{item.title}
</Link>
)
})
NavigationItem.displayName = 'NavigationItem'
export default NavigationItem

View File

@@ -0,0 +1,21 @@
import { docsConfig } from '@/config/docs'
import NavigationItem from '@/components/side-navigation-item'
function SideNavigation() {
return (
<nav className="min-w-[220px]">
{docsConfig.sidebarNav.map((section, i) => (
<div key={`${section.title}-${i}`} className="pb-10 space-y-0.5">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-wider">
{section.title}
</div>
{section.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
))}
</nav>
)
}
export default SideNavigation

View File

@@ -0,0 +1,61 @@
import { siteConfig } from '@/config/site'
export function SiteFooter() {
return (
<footer className="py-6 md:px-8 md:py-0 mx-auto border-r border-l border-b w-full max-w-site">
<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{' '}
<a
href={siteConfig.links.twitter}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
Supabase
</a>
. The source code is available on{' '}
<a
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
GitHub
</a>
.
</p>
<p className="text-balance text-center text-sm leading-loose text-foreground-muted">
Site inspired by{' '}
<a
href={siteConfig.links.credits.radix}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
Radix
</a>
,{' '}
<a
href={siteConfig.links.credits.shadcn}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
shadcn/ui
</a>{' '}
and{' '}
<a
href={siteConfig.links.credits.geist}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
Geist
</a>
.
</p>
</div>
</footer>
)
}

View File

@@ -0,0 +1,206 @@
import { Doc } from '@/.contentlayer/generated'
import Link from 'next/link'
import { forwardRef } from 'react'
import { ExternalLink } from 'lucide-react'
import { Button } from 'ui'
import { cn } from 'ui/src/lib/utils/cn'
import Image from 'next/image'
const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> & { doc: Doc }>(
({ doc, children, ...props }, ref) => {
const label = {
radix: 'Radix UI',
vaul: 'Vaul',
shadcn: 'ui.shadcn',
}
const ShadcnPanel = () => {
if (doc.source?.shadcn) {
return (
<div
className={cn(
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md',
props.className
)}
{...props}
>
<div className="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" className="h-6 w-6">
<rect width="256" height="256" fill="none"></rect>
<line
x1="208"
y1="128"
x2="128"
y2="208"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
></line>
<line
x1="192"
y1="40"
x2="40"
y2="192"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
></line>
</svg>
<span className="hidden font-bold sm:inline-block">shadcn/ui</span>
</div>
<span className="text-foreground-light text-sm">
This component is based on ui.shadcn
</span>
</div>
)
}
}
const VaulPanel = () => {
if (doc.source?.vaul) {
return (
<div
className={cn(
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md',
props.className
)}
{...props}
>
<div className="flex items-center gap-2">
<Image
width={24}
height={24}
src="https://avatars.githubusercontent.com/u/36730035?s=48&v=4"
alt="Vaul"
className="h-6 w-6 rounded-full"
/>
<span className="hidden font-bold sm:inline-block">vaul</span>
</div>
<span className="text-foreground-light text-sm">
This component is based on vaul by emilkowalski
</span>
</div>
)
}
}
const InputOtp = () => {
if (doc.source?.inputOtp) {
return (
<div
className={cn(
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md',
props.className
)}
{...props}
>
<div className="flex items-center gap-2">
<Image
width={24}
height={24}
src="https://avatars.githubusercontent.com/u/10366880?s=48&v=4"
alt="inputOtp"
className="h-6 w-6 rounded-full"
/>
<span className="hidden font-bold sm:inline-block">input-otp</span>
</div>
<span className="text-foreground-light text-sm">
This component is based on input-otp by guilhermerodz
</span>
</div>
)
}
}
const RadixPanel = () => {
if (doc.source?.radix) {
return (
<div
className={cn(
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md',
props.className
)}
{...props}
>
<svg
width="76"
height="24"
viewBox="0 0 76 24"
fill="currentcolor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M43.9022 20.0061H46.4499C46.2647 19.0375 46.17 18.1161 46.17 17.0058V12.3753C46.17 9.25687 44.3893 7.72127 41.1943 7.72127C38.3003 7.72127 36.3324 9.23324 36.0777 11.8083H38.9254C39.0181 10.698 39.8052 9.96561 41.1017 9.96561C42.4446 9.96561 43.3243 10.6743 43.3243 12.1391V12.7061L39.8052 13.1077C37.4206 13.3912 35.5684 14.3834 35.5684 16.7931C35.5684 18.9666 37.2353 20.2659 39.5274 20.2659C41.4027 20.2659 42.9845 19.4863 43.6401 18.1161C43.6689 18.937 43.9022 20.0061 43.9022 20.0061ZM40.3377 18.1634C39.157 18.1634 38.5087 17.5727 38.5087 16.6278C38.5087 15.3757 39.4579 15.0922 40.7082 14.9268L43.3243 14.6197V15.352C43.3243 17.242 41.8658 18.1634 40.3377 18.1634ZM56.2588 20.0061H59.176V3H56.2125V9.96561C55.6569 8.76075 54.3141 7.72127 52.4851 7.72127C49.3058 7.72127 47.099 10.2963 47.099 14.0054C47.099 17.7381 49.3058 20.2896 52.4851 20.2896C54.2678 20.2896 55.68 19.2973 56.2588 18.0925V20.0061ZM56.282 14.218C56.282 16.5569 55.1938 18.0689 53.3185 18.0689C51.3969 18.0689 50.1856 16.486 50.1856 14.0054C50.1856 11.5485 51.3969 9.94198 53.3185 9.94198C55.1938 9.94198 56.282 11.454 56.282 13.7928V14.218ZM60.9066 5.97304H64.0553V3.01996H60.9066V5.97304ZM60.9992 20.0061H63.9627V8.00476H60.9992V20.0061ZM67.6638 20.0061L70.6041 15.8954L73.5212 20.0061H76.9246L72.3636 13.7219L76.5542 8.00476H73.3823L70.7661 11.7138L68.1731 8.00476H64.7697L69.0066 13.8637L64.4919 20.0061H67.6638Z"></path>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M24.9132 20V14.0168H28.7986L32.4513 20H35.7006L31.6894 13.5686C33.5045 12.986 35.0955 11.507 35.0955 9.01961C35.0955 5.7479 32.7994 4 28.9571 4H22V20H24.9132ZM24.9132 6.35294V11.6863H28.821C31.0395 11.6863 32.1599 10.7675 32.1599 9.01961C32.1599 7.27171 30.9395 6.35294 28.621 6.35294H24.9132Z"
></path>
<path d="M7 23C3.13401 23 0 19.6422 0 15.5C0 11.3578 3.13401 8 7 8V23Z"></path>
<path d="M7 0H0V7H7V0Z"></path>
<path d="M11.5 7C13.433 7 15 5.433 15 3.5C15 1.567 13.433 0 11.5 0C9.56704 0 8 1.567 8 3.5C8 5.433 9.56704 7 11.5 7Z"></path>
</svg>
<div className="flex flex-row items-center justify-between text-sm w-full">
<span className="text-foreground-light text-sm">This component uses Radix UI</span>
{doc.links ? (
<div className="flex items-center gap-2 justify-end">
{doc.links?.doc && (
<Button
type="outline"
className="rounded-full"
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
>
<Link
href={doc.links.doc}
target="_blank"
rel="noreferrer"
// className={cn(buttonVariants({ variant: 'default' }), 'gap-1')}
>
Docs
</Link>
</Button>
)}
{doc.links?.api && (
<Button
type="outline"
className="rounded-full"
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
>
<Link
href={doc.links.api}
target="_blank"
rel="noreferrer"
// className={cn(badgeVariants({ variant: 'default' }), 'gap-1')}
>
API Reference
</Link>
</Button>
)}
</div>
) : null}
</div>
</div>
)
}
}
return (
<div className="flex flex-col -space-y-px">
<RadixPanel />
<VaulPanel />
<InputOtp />
{/* <ShadcnPanel /> */}
</div>
)
}
)
SourcePanel.displayName = 'SourcePanel'
export { SourcePanel }

View File

@@ -0,0 +1,20 @@
'use client'
import * as React from 'react'
import { useConfig } from '@/hooks/use-config'
import { Style } from '@/registry/styles'
interface StyleWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
styleName?: Style['name']
}
export function StyleWrapper({ styleName, children }: StyleWrapperProps) {
const [config] = useConfig()
if (!styleName || config.style === styleName) {
return <>{children}</>
}
return null
}

View File

@@ -0,0 +1,54 @@
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
import SVG from 'react-inlinesvg'
import { RadioGroupLargeItem_Shadcn_, RadioGroup_Shadcn_, Theme, singleThemes } from 'ui'
const ThemeSettings = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
/**
* Avoid Hydration Mismatch
* https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
*/
// useEffect only runs on the client, so now we can safely show the UI
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
function SingleThemeSelection() {
return (
<form className="py-8">
<RadioGroup_Shadcn_
name="theme"
onValueChange={setTheme}
aria-label="Choose a theme"
defaultValue={theme}
value={theme}
className="flex flex-wrap gap-3"
>
{singleThemes.map((theme: Theme) => (
<RadioGroupLargeItem_Shadcn_ key={theme.value} value={theme.value} label={theme.name}>
<SVG src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/themes/${theme.value}.svg`} />
</RadioGroupLargeItem_Shadcn_>
))}
</RadioGroup_Shadcn_>
</form>
)
}
return (
<>
<SingleThemeSelection />
</>
)
}
export { ThemeSettings }

View File

@@ -0,0 +1,92 @@
'use client'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
import SVG from 'react-inlinesvg'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
RadioGroupLargeItem_Shadcn_,
RadioGroup_Shadcn_,
Theme,
singleThemes,
} from 'ui'
const ThemeSwitcherDropdown = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme, resolvedTheme } = useTheme()
/**
* Avoid Hydration Mismatch
* https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
*/
// useEffect only runs on the client, so now we can safely show the UI
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
function SingleThemeSelection() {
return (
<form>
<RadioGroup_Shadcn_
name="theme"
onValueChange={setTheme}
aria-label="Choose a theme"
defaultValue={theme}
value={theme}
className="flex flex-wrap gap-3"
></RadioGroup_Shadcn_>
</form>
)
}
const iconClasses = 'text-foreground-light group-data-[state=open]:text-foreground'
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="text"
size="tiny"
className="px-1 group"
icon={
resolvedTheme?.includes('light') ? (
<Sun className={iconClasses} />
) : (
<Moon className={iconClasses} />
)
}
></Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end">
<DropdownMenuLabel>Theme</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={theme}
onValueChange={(themeValue) => setTheme(themeValue)}
>
{singleThemes.map((theme: Theme) => (
<DropdownMenuRadioItem key={theme.value} value={theme.value}>
{theme.name}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</>
)
}
export { ThemeSwitcherDropdown }

View File

@@ -0,0 +1,107 @@
// @ts-nocheck
'use client'
import * as React from 'react'
import { TableOfContents } from '@/lib/toc'
import { cn } from '@/lib/utils'
import { useMounted } from '@/hooks/use-mounted'
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
}

View File

@@ -0,0 +1,17 @@
'use client'
import { Search } from 'lucide-react'
import { Input } from 'ui-patterns/DataInputs/Input'
function TopNavigationSearch() {
return (
<Input
size="small"
className="transition w-64 rounded-full hover:bg-surface-200 hover:border-foreground-muted cursor-pointer"
icon={<Search size={14} />}
placeholder="Search Design System..."
/>
)
}
export { TopNavigationSearch }

View File

@@ -0,0 +1,38 @@
// import { docsConfig } from '@/config/docs'
import Link from 'next/link'
import { DesignSystemMarks } from './design-system-marks'
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
import { TopNavigationSearch } from './top-navigation-search'
import { CommandMenu } from './command-menu'
function TopNavigation() {
return (
<header className="sticky top-0 z-50 w-full border-t bg-studio/95 backdrop-blur supports-[backdrop-filter]:bg-studio/60">
<div className="absolute border-b border-dashed w-full top-[3.4rem] -z-10"></div>
<nav className="h-14 w-full flex">
<div className="max-w-site border-b w-full flex flex-row items-center gap-6 mx-auto px-6 border-r border-l justify-between">
<div className="flex items-center gap-8">
<Link href="/">
<h1>Supabase Design System</h1>
</Link>
<DesignSystemMarks />
</div>
{/* {docsConfig.mainNav.map((section) => (
<>
<div className="font-mono uppercase text-xs text-foreground-lighter">
{section.title}
</div>
</>
))} */}
<div className="flex items-center gap-8">
{/* <TopNavigationSearch /> */}
<CommandMenu />
<ThemeSwitcherDropdown />
</div>
</div>
</nav>
</header>
)
}
export default TopNavigation

View File

@@ -0,0 +1,335 @@
import { MainNavItem, SidebarNavItem } from 'types/nav'
interface DocsConfig {
mainNav?: MainNavItem[]
sidebarNav: SidebarNavItem[]
}
export const docsConfig: DocsConfig = {
// mainNav: [
// {
// title: 'Documentation',
// href: '/docs',
// },
// {
// title: 'Components',
// href: '/docs/components/accordion',
// },
// {
// title: 'Themes',
// href: '/themes',
// },
// {
// title: 'Examples',
// href: '/examples',
// },
// {
// title: 'Blocks',
// href: '/blocks',
// },
// ],
sidebarNav: [
{
title: 'Getting Started',
items: [
{
title: 'Introduction',
href: '/docs',
items: [],
},
{
title: 'Color usage',
href: '/docs/color-usage',
items: [],
},
{
title: 'Tailwind classes',
href: '/docs/tailwind-classes',
items: [],
},
{
title: 'Theming',
href: '/docs/theming',
items: [],
},
{
title: 'Icons',
href: '/docs/icons',
items: [],
},
{
title: 'Figma',
href: '/docs/figma',
items: [],
},
{
title: 'Changelog',
href: '/docs/changelog',
items: [],
},
],
},
{
title: 'Fragment Components',
items: [
{
title: 'Text Confirm Dialog',
href: '/docs/fragments/text-confirm-dialog',
items: [],
},
{
title: 'Form Item Layout',
href: '/docs/fragments/form-item-layout',
items: [],
},
],
},
{
title: 'Atom Components',
items: [
{
title: 'Accordion',
href: '/docs/components/accordion',
items: [],
},
{
title: 'Alert',
href: '/docs/components/alert',
items: [],
},
{
title: 'Alert Dialog',
href: '/docs/components/alert-dialog',
items: [],
},
{
title: 'Aspect Ratio',
href: '/docs/components/aspect-ratio',
items: [],
},
{
title: 'Avatar',
href: '/docs/components/avatar',
items: [],
},
{
title: 'Badge',
href: '/docs/components/badge',
items: [],
},
{
title: 'Breadcrumb',
href: '/docs/components/breadcrumb',
items: [],
label: 'New',
},
{
title: 'Button',
href: '/docs/components/button',
items: [],
},
{
title: 'Calendar',
href: '/docs/components/calendar',
items: [],
},
{
title: 'Card',
href: '/docs/components/card',
items: [],
},
{
title: 'Carousel',
href: '/docs/components/carousel',
items: [],
},
{
title: 'Checkbox',
href: '/docs/components/checkbox',
items: [],
},
{
title: 'Collapsible',
href: '/docs/components/collapsible',
items: [],
},
{
title: 'Combobox',
href: '/docs/components/combobox',
items: [],
},
{
title: 'Command',
href: '/docs/components/command',
items: [],
},
{
title: 'Context Menu',
href: '/docs/components/context-menu',
items: [],
},
{
title: 'Data Table',
href: '/docs/components/data-table',
items: [],
},
{
title: 'Date Picker',
href: '/docs/components/date-picker',
items: [],
},
{
title: 'Dialog',
href: '/docs/components/dialog',
items: [],
},
{
title: 'Drawer',
href: '/docs/components/drawer',
items: [],
},
{
title: 'Dropdown Menu',
href: '/docs/components/dropdown-menu',
items: [],
},
{
title: 'Form',
href: '/docs/components/form',
items: [],
},
{
title: 'Hover Card',
href: '/docs/components/hover-card',
items: [],
},
{
title: 'Input',
href: '/docs/components/input',
items: [],
},
{
title: 'Input OTP',
href: '/docs/components/input-otp',
items: [],
label: 'New',
},
{
title: 'Label',
href: '/docs/components/label',
items: [],
},
{
title: 'Menubar',
href: '/docs/components/menubar',
items: [],
},
{
title: 'Navigation Menu',
href: '/docs/components/navigation-menu',
items: [],
},
{
title: 'Pagination',
href: '/docs/components/pagination',
items: [],
},
{
title: 'Popover',
href: '/docs/components/popover',
items: [],
},
{
title: 'Progress',
href: '/docs/components/progress',
items: [],
},
{
title: 'Radio Group',
href: '/docs/components/radio-group',
items: [],
},
{
title: 'Resizable',
href: '/docs/components/resizable',
items: [],
},
{
title: 'Scroll Area',
href: '/docs/components/scroll-area',
items: [],
},
{
title: 'Select',
href: '/docs/components/select',
items: [],
},
{
title: 'Separator',
href: '/docs/components/separator',
items: [],
},
{
title: 'Sheet',
href: '/docs/components/sheet',
items: [],
},
{
title: 'Skeleton',
href: '/docs/components/skeleton',
items: [],
},
{
title: 'Slider',
href: '/docs/components/slider',
items: [],
},
{
title: 'Sonner',
href: '/docs/components/sonner',
items: [],
},
{
title: 'Switch',
href: '/docs/components/switch',
items: [],
},
{
title: 'Table',
href: '/docs/components/table',
items: [],
},
{
title: 'Tabs',
href: '/docs/components/tabs',
items: [],
},
{
title: 'Textarea',
href: '/docs/components/textarea',
items: [],
},
{
title: 'Toast',
href: '/docs/components/toast',
items: [],
},
{
title: 'Toggle',
href: '/docs/components/toggle',
items: [],
},
{
title: 'Toggle Group',
href: '/docs/components/toggle-group',
items: [],
},
{
title: 'Tooltip',
href: '/docs/components/tooltip',
items: [],
},
],
},
],
}

View File

@@ -0,0 +1,17 @@
export const siteConfig = {
name: 'Supabase Design System',
url: 'https://supabase.com/design-system',
ogImage: 'https://supabase.com/design-system/og.jpg',
description: 'Design System of Supabase',
links: {
twitter: 'https://twitter.com/supabase',
github: 'https://github.com/supabase/supabase/apps/design-system',
credits: {
radix: 'https://www.radix-ui.com/themes/docs/overview/getting-started',
shadcn: 'https://ui.shadcn.com/',
geist: 'https://vercel.com/geist/introduction',
},
},
}
export type SiteConfig = typeof siteConfig

View File

@@ -0,0 +1,31 @@
---
title: Changelog
description: Latest updates and announcements.
toc: false
---
## May 2024 - Introducing Design System
We're introducing **Design System**, a home for Design related resources for Supabase
<a href="/blocks">
<Image
src="/images/lift-mode-light.png"
width="1432"
height="1050"
alt="Lift Mode"
className="border dark:hidden shadow-sm rounded-lg overflow-hidden mt-6 w-full"
/>
<Image
src="/images/lift-mode-dark.png"
width="1432"
height="1069"
alt="Lift Mode"
className="border hidden dark:block shadow-sm rounded-lg overflow-hidden mt-6 w-full"
/>
<span class="sr-only">View the blocks library</span>
</a>
With Lift Mode, you'll be able to copy the smaller components that make up a block template, like cards, buttons, and forms, and paste them directly into your project.
Visit the [Blocks](/blocks) page to try it out.

View File

@@ -0,0 +1,74 @@
---
title: Color usage
description: Colors system breakdown with best practise
---
Colors available in the Supabase Design System
These are examples of using colors with shorthands.
## Background
<Colors definition={'background'} />
### App backgrounds
We use backgrounds in 2 different ways. In the ./www and ./docs sites, we use a darker background, so we have an extra background color we can use
```jsx
/**
* ./www background color
* ./docs background color
*/
<body className="bg">{children}</body>
/**
* ./studio background color
*/
<body className="bg-studio">{children}</body>
```
### Backgrounds and Surfaces
#### `./apps/www` + `./apps/docs`
We use surfaces in 2 different ways. In the ./www and ./docs sites, we use a darker background, so we have an extra surface color we can use
<CodeFragment name="color-usage-surface-www-and-docs" />
#### `./apps/studio`
For the studio (dashbaord) we can use `bg-surface-100`, `bg-surface-200`, `bg-surface-300`
<CodeFragment name="color-usage-surface-studio" />
#### Data grid and frame space
Data grids use an alternative background color for empty space to add depth to the layout.
The background of the empty space is the same background as used in `./apps/docs` and `./apps/www` - although; the color has been mapped to `bg-alternative` so it works well across different themes.
<CodeFragment name="color-usage-surface-studio-frame" />
Dealing with large areas of emmpty space in data display should also be catered for. You can use the `bg-200` or `bg` class to fill the space.
### Overlays
We use the `./bg-overlay` background color for overlays.
This is not to be confused with `Dialogs`, they require to use the same app background color as the site.
## Border
<Colors definition={'border'} />
## Text
These can also be accessed with `foreground`. Like `text-foreground-light`.
<Colors definition={'text'} />
## Other Colors
These can also be accessed with `foreground`. Like `text-foreground-light`.
<Colors definition={'colors'} />
```

View File

@@ -0,0 +1,139 @@
---
title: Accordion
description: A vertically stacked set of interactive headings that each reveal a section of content.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/accordion
api: https://www.radix-ui.com/docs/primitives/components/accordion#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview
name="accordion-demo"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
peekCode
wide
/>
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
<Steps>
<Step>Run the following command:</Step>
```bash
npx shadcn-ui@latest add accordion
```
<Step>Update `tailwind.config.js`</Step>
Add the following animations to your `tailwind.config.js` file:
```js title="tailwind.config.js" {5-18}
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
}
```
</Steps>
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-accordion
```
<Step>Copy and paste the following code into your project.</Step>
{/* <ComponentSource name="accordion" /> */}
<Step>Update the import paths to match your project setup.</Step>
<Step>Update `tailwind.config.js`</Step>
Add the following animations to your `tailwind.config.js` file:
```js title="tailwind.config.js" {5-18}
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
}
```
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion'
```
```tsx
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
</AccordionItem>
</Accordion>
```

View File

@@ -0,0 +1,65 @@
---
title: Alert
description: Displays a callout for user attention.
component: true
---
<ComponentPreview name="alert-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add alert
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="alert" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
```
```tsx
<Alert>
<Terminal className="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
You can add components and dependencies to your app using the cli.
</AlertDescription>
</Alert>
```
## Examples
### Default
<ComponentPreview name="alert-demo" peekCode wide />
### Destructive
<ComponentPreview name="alert-destructive" />

View File

@@ -0,0 +1,87 @@
---
title: Alert Dialog
description: A modal dialog that interrupts the user with important content and expects a response.
featured: true
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/alert-dialog
api: https://www.radix-ui.com/docs/primitives/components/alert-dialog#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="alert-dialog-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add alert-dialog
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-alert-dialog
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="alert-dialog" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog'
```
```tsx
<AlertDialog>
<AlertDialogTrigger>Open</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
```

View File

@@ -0,0 +1,79 @@
---
title: Alert
description: Displays a callout for user attention.
component: true
source:
radix: true
shadcn: true
---
<ComponentPreview
name="alert-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.'."
peekCode
wide
/>
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add alert
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="alert" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
```
```tsx
<Alert>
<Terminal className="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
You can add components and dependencies to your app using the cli.
</AlertDescription>
</Alert>
```
## Examples
### Default
<ComponentPreview
name="alert-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.'."
/>
### Destructive
<ComponentPreview
name="alert-destructive"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>

View File

@@ -0,0 +1,67 @@
---
title: Aspect Ratio
description: Displays content within a desired ratio.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/aspect-ratio
api: https://www.radix-ui.com/docs/primitives/components/aspect-ratio#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="aspect-ratio-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add aspect-ratio
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-aspect-ratio
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="aspect-ratio" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import Image from 'next/image'
import { AspectRatio } from '@/components/ui/aspect-ratio'
```
```tsx
<div className="w-[450px]">
<AspectRatio ratio={16 / 9}>
<Image src="..." alt="Image" className="rounded-md object-cover" />
</AspectRatio>
</div>
```

View File

@@ -0,0 +1,64 @@
---
title: Avatar
description: An image element with a fallback for representing the user.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/avatar
api: https://www.radix-ui.com/docs/primitives/components/avatar#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="avatar-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add avatar
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-avatar
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="avatar" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
```
```tsx
<Avatar>
<AvatarImage src="https://github.com/mildtomato.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
```

View File

@@ -0,0 +1,87 @@
---
title: Badge
description: Displays a badge or a component that looks like a badge.
component: true
source:
shadcn: true
---
<ComponentPreview name="badge-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add badge
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="badge" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Badge } from '@/components/ui/badge'
```
```tsx
<Badge variant="outline">Badge</Badge>
```
### Link
You can use the `badgeVariants` helper to create a link that looks like a badge.
```tsx
import { badgeVariants } from '@/components/ui/badge'
```
```tsx
<Link className={badgeVariants({ variant: 'outline' })}>Badge</Link>
```
## Examples
### Default
<ComponentPreview name="badge-demo" peekCode wide />
---
### Secondary
<ComponentPreview name="badge-secondary" />
---
### Outline
<ComponentPreview name="badge-outline" />
---
### Destructive
<ComponentPreview name="badge-destructive" />

View File

@@ -0,0 +1,190 @@
---
title: Breadcrumb
description: Displays the path to the current resource using a hierarchy of links.
component: true
---
<ComponentPreview name="breadcrumb-demo" className="[&_.preview]:p-2" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add breadcrumb
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="breadcrumb" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
```
```tsx
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
```
## Examples
### Custom separator
Use a custom component as `children` for `<BreadcrumbSeparator />` to create a custom separator.
<ComponentPreview name="breadcrumb-separator" />
```tsx showLineNumbers {1,10-12}
import { Slash } from "lucide-react"
...
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<Slash />
</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
```
---
### Dropdown
You can compose `<BreadcrumbItem />` with a `<DropdownMenu />` to create a dropdown in the breadcrumb.
<ComponentPreview name="breadcrumb-dropdown" className="[&_.preview]:p-2" />
```tsx showLineNumbers {1-6,11-21}
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
...
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
Components
<ChevronDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
```
---
### Collapsed
We provide a `<BreadcrumbEllipsis />` component to show a collapsed state when the breadcrumb is too long.
<ComponentPreview name="breadcrumb-ellipsis" className="[&_.preview]:p-2" />
```tsx showLineNumbers {1,9}
import { BreadcrumbEllipsis } from "@/components/ui/breadcrumb"
...
<Breadcrumb>
<BreadcrumbList>
{/* ... */}
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
{/* ... */}
</BreadcrumbList>
</Breadcrumb>
```
---
### Link component
To use a custom link component from your routing library, you can use the `asChild` prop on `<BreadcrumbLink />`.
<ComponentPreview name="breadcrumb-link" />
```tsx showLineNumbers {1,8-10}
import { Link } from "next/link"
...
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/">Home</Link>
</BreadcrumbLink>
</BreadcrumbItem>
{/* ... */}
</BreadcrumbList>
</Breadcrumb>
```
---
### Responsive
Here's an example of a responsive breadcrumb that composes `<BreadcrumbItem />` with `<BreadcrumbEllipsis />`, `<DropdownMenu />`, and `<Drawer />`.
It displays a dropdown on desktop and a drawer on mobile.
<ComponentPreview name="breadcrumb-responsive" className="[&_.preview]:p-2" />

View File

@@ -0,0 +1,118 @@
---
title: Button
description: Displays a button or a component that looks like a button.
featured: true
component: true
---
<ComponentPreview name="button-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add button
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-slot
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="button" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Button } from '@/components/ui/button'
```
```tsx
<Button variant="outline">Button</Button>
```
## Link
You can use the `buttonVariants` helper to create a link that looks like a button.
```tsx
import { buttonVariants } from '@/components/ui/button'
```
```tsx
<Link className={buttonVariants({ variant: 'outline' })}>Click here</Link>
```
Alternatively, you can set the `asChild` parameter and nest the link component.
```tsx
<Button asChild>
<Link href="/login">Login</Link>
</Button>
```
## Examples
### Primary
<ComponentPreview name="button-demo" peekCode wide />
### Secondary
<ComponentPreview name="button-secondary" />
### Destructive
<ComponentPreview name="button-destructive" />
### Outline
<ComponentPreview name="button-outline" />
### Ghost
<ComponentPreview name="button-ghost" />
### Link
<ComponentPreview name="button-link" />
### Icon
<ComponentPreview name="button-icon" />
### With Icon
<ComponentPreview name="button-with-icon" />
### Loading
<ComponentPreview name="button-loading" />
### As Child
<ComponentPreview name="button-as-child" />

View File

@@ -0,0 +1,81 @@
---
title: Calendar
description: A date field component that allows users to enter and edit date.
component: true
links:
doc: https://react-day-picker.js.org
source:
shadcn: true
---
<ComponentPreview name="calendar-demo" peekCode wide />
## About
The `Calendar` component is built on top of [React DayPicker](https://react-day-picker.js.org).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add calendar
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install react-day-picker date-fns
```
<Step>Add the `Button` component to your project.</Step>
The `Calendar` component uses the `Button` component. Make sure you have it installed in your project.
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="calendar" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Calendar } from '@/components/ui/calendar'
```
```tsx
const [date, setDate] = React.useState<Date | undefined>(new Date())
return <Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border" />
```
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
## Date Picker
You can use the `<Calendar>` component to build a date picker. See the [Date Picker](/docs/components/date-picker) page for more information.
## Examples
### Form
<ComponentPreview name="calendar-form" />

View File

@@ -0,0 +1,71 @@
---
title: Card
description: Displays a card with header, content, and footer.
component: true
---
<ComponentPreview name="card-with-form" />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add card
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="card" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card'
```
```tsx
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<p>Card Footer</p>
</CardFooter>
</Card>
```
## Examples
<ComponentPreview name="card-demo" peekCode wide />

View File

@@ -0,0 +1,277 @@
---
title: Carousel
description: A carousel with motion and swipe built using Embla.
component: true
links:
doc: https://www.embla-carousel.com/get-started/react
api: https://www.embla-carousel.com/api
---
<ComponentPreview name="carousel-demo" peekCode wide />
## About
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add carousel
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install embla-carousel-react
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="carousel" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel'
```
```tsx
<Carousel>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
```
## Examples
### Sizes
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
<ComponentPreview name="carousel-size" />
```tsx title="Example" showLineNumbers {4-6}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
<CarouselItem className="basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
```tsx title="Responsive" showLineNumbers {4-6}
// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
### Spacing
To set the spacing between the items, we use a `pl-[VALUE]` utility on the `<CarouselItem />` and a negative `-ml-[VALUE]` on the `<CarouselContent />`.
<Callout className="mt-6">
**Why:** I tried to use the `gap` property or a `grid` layout on the `
<CarouselContent />` but it required a lot of math and mental effort to get the
spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to
use.
You can always adjust this in your own project if you need to.
</Callout>
<ComponentPreview name="carousel-spacing" />
```tsx title="Example" showLineNumbers /-ml-4/ /pl-4/
<Carousel>
<CarouselContent className="-ml-4">
<CarouselItem className="pl-4">...</CarouselItem>
<CarouselItem className="pl-4">...</CarouselItem>
<CarouselItem className="pl-4">...</CarouselItem>
</CarouselContent>
</Carousel>
```
```tsx title="Responsive" showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
<Carousel>
<CarouselContent className="-ml-2 md:-ml-4">
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
<CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
</CarouselContent>
</Carousel>
```
### Orientation
Use the `orientation` prop to set the orientation of the carousel.
<ComponentPreview name="carousel-orientation" />
```tsx showLineNumbers /vertical | horizontal/
<Carousel orientation="vertical | horizontal">
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
```
## Options
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
```tsx showLineNumbers {2-5}
<Carousel
opts={{
align: 'start',
loop: true,
}}
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
```
## API
Use a state and the `setApi` props to get an instance of the carousel API.
<ComponentPreview name="carousel-api" />
```tsx showLineNumbers {1,4,22}
import { type CarouselApi } from '@/components/ui/carousel'
export function Example() {
const [api, setApi] = React.useState<CarouselApi>()
const [current, setCurrent] = React.useState(0)
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (!api) {
return
}
setCount(api.scrollSnapList().length)
setCurrent(api.selectedScrollSnap() + 1)
api.on('select', () => {
setCurrent(api.selectedScrollSnap() + 1)
})
}, [api])
return (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
)
}
```
## Events
You can listen to events using the api instance from `setApi`.
```tsx showLineNumbers {1,4-14,16}
import { type CarouselApi } from '@/components/ui/carousel'
export function Example() {
const [api, setApi] = React.useState<CarouselApi>()
React.useEffect(() => {
if (!api) {
return
}
api.on('select', () => {
// Do something on select.
})
}, [api])
return (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
)
}
```
See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for more information on using events.
## Plugins
You can use the `plugins` prop to add plugins to the carousel.
```ts showLineNumbers {1,6-10}
import Autoplay from "embla-carousel-autoplay"
export function Example() {
return (
<Carousel
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
// ...
</Carousel>
)
}
```
<ComponentPreview name="carousel-plugin" />
See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins.

View File

@@ -0,0 +1,77 @@
---
title: Checkbox
description: A control that allows the user to toggle between checked and not checked.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/checkbox
api: https://www.radix-ui.com/docs/primitives/components/checkbox#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="checkbox-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add checkbox
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-checkbox
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="checkbox" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Checkbox } from '@/components/ui/checkbox'
```
```tsx
<Checkbox />
```
## Examples
### With text
<ComponentPreview name="checkbox-with-text" />
### Disabled
<ComponentPreview name="checkbox-disabled" />
### Form
<ComponentPreview name="checkbox-form-single" />
<ComponentPreview name="checkbox-form-multiple" />

View File

@@ -0,0 +1,67 @@
---
title: Collapsible
description: An interactive component which expands/collapses a panel.
component: true
featured: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/collapsible
api: https://www.radix-ui.com/docs/primitives/components/collapsible#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="collapsible-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add collapsible
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-collapsible
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="collapsible" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
```
```tsx
<Collapsible>
<CollapsibleTrigger>Can I use this in my project?</CollapsibleTrigger>
<CollapsibleContent>
Yes. Free to use for personal and commercial projects. No attribution required.
</CollapsibleContent>
</Collapsible>
```

View File

@@ -0,0 +1,131 @@
---
title: Combobox
description: Autocomplete input and command palette with a list of suggestions.
component: true
source:
shadcn: true
---
<ComponentPreview name="combobox-demo" peekCode />
## Installation
The Combobox is built using a composition of the `<Popover />` and the `<Command />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Command](/docs/components/command#installation) components.
## Usage
```tsx
'use client'
import * as React from 'react'
import { Check, ChevronsUpDown } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from '@/components/ui/command'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
const frameworks = [
{
value: 'next.js',
label: 'Next.js',
},
{
value: 'sveltekit',
label: 'SvelteKit',
},
{
value: 'nuxt.js',
label: 'Nuxt.js',
},
{
value: 'remix',
label: 'Remix',
},
{
value: 'astro',
label: 'Astro',
},
]
export function ComboboxDemo() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState('')
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[200px] justify-between"
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: 'Select framework...'}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? '' : currentValue)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0'
)}
/>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
)
}
```
## Examples
### Combobox
<ComponentPreview name="combobox-demo" peekCode wide />
### Popover
<ComponentPreview name="combobox-popover" />
### Dropdown menu
<ComponentPreview name="combobox-dropdown-menu" />
### Responsive
You can create a responsive combobox by using the `<Popover />` on desktop and the `<Drawer />` components on mobile.
<ComponentPreview name="combobox-responsive" />
### Form
<ComponentPreview name="combobox-form" />

View File

@@ -0,0 +1,138 @@
---
title: Command
description: Fast, composable, unstyled command menu for React.
component: true
links:
doc: https://cmdk.paco.me
source:
shadcn: true
---
<ComponentPreview
name="command-demo"
align="start"
className="[&_.preview>div]:max-w-[450px]"
peekCode
wide
/>
## About
The `<Command />` component uses the [`cmdk`](https://cmdk.paco.me) component by [pacocoursey](https://twitter.com/pacocoursey).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add command
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install cmdk
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="command" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from '@/components/ui/command'
```
```tsx
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>Profile</CommandItem>
<CommandItem>Billing</CommandItem>
<CommandItem>Settings</CommandItem>
</CommandGroup>
</CommandList>
</Command>
```
## Examples
### Dialog
<ComponentPreview name="command-dialog" />
To show the command menu in a dialog, use the `<CommandDialog />` component.
```tsx
export function CommandMenu() {
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [])
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
)
}
```
### Combobox
You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information.

View File

@@ -0,0 +1,74 @@
---
title: Context Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/context-menu
api: https://www.radix-ui.com/docs/primitives/components/context-menu#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="context-menu-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add context-menu
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-context-menu
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="context-menu" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from '@/components/ui/context-menu'
```
```tsx
<ContextMenu>
<ContextMenuTrigger>Right click</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Profile</ContextMenuItem>
<ContextMenuItem>Billing</ContextMenuItem>
<ContextMenuItem>Team</ContextMenuItem>
<ContextMenuItem>Subscription</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
```

View File

@@ -0,0 +1,842 @@
---
title: Data Table
description: Powerful table and datagrids built using TanStack Table.
component: true
links:
doc: https://tanstack.com/table/v8/docs/guide/introduction
---
<ComponentPreview name="data-table-demo" peekCode wide />
## Introduction
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/guide/introduction#what-is-headless-ui) provides.
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
We'll start with the basic `<Table />` component and build a complex data table from scratch.
<Callout className="mt-4">
**Tip:** If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.
</Callout>
## Table of Contents
This guide will show you how to use [TanStack Table](https://tanstack.com/table) and the `<Table />` component to build your own custom data table. We'll cover the following topics:
- [Basic Table](#basic-table)
- [Row Actions](#row-actions)
- [Pagination](#pagination)
- [Sorting](#sorting)
- [Filtering](#filtering)
- [Visibility](#visibility)
- [Row Selection](#row-selection)
- [Reusable Components](#reusable-components)
## Installation
1. Add the `<Table />` component to your project:
```bash
npx shadcn-ui@latest add table
```
2. Add `tanstack/react-table` dependency:
```bash
npm install @tanstack/react-table
```
## Prerequisites
We are going to build a table to show recent payments. Here's what our data looks like:
```tsx showLineNumbers
type Payment = {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
export const payments: Payment[] = [
{
id: '728ed52f',
amount: 100,
status: 'pending',
email: 'm@example.com',
},
{
id: '489e1d42',
amount: 125,
status: 'processing',
email: 'example@gmail.com',
},
// ...
]
```
## Project Structure
Start by creating the following file structure:
```txt
app
└── payments
├── columns.tsx
├── data-table.tsx
└── page.tsx
```
I'm using a Next.js example here but this works for any other React framework.
- `columns.tsx` (client component) will contain our column definitions.
- `data-table.tsx` (client component) will contain our `<DataTable />` component.
- `page.tsx` (server component) is where we'll fetch data and render our table.
## Basic Table
Let's start by building a basic table.
<Steps>
### Column Definitions
First, we'll define our columns.
```tsx showLineNumbers title="app/payments/columns.tsx" {3,14-27}
'use client'
import { ColumnDef } from '@tanstack/react-table'
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type Payment = {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'status',
header: 'Status',
},
{
accessorKey: 'email',
header: 'Email',
},
{
accessorKey: 'amount',
header: 'Amount',
},
]
```
<Callout className="mt-4">
**Note:** Columns are where you define the core of what your table
will look like. They define the data that will be displayed, how it will be
formatted, sorted and filtered.
</Callout>
### `<DataTable />` component
Next, we'll create a `<DataTable />` component to render our table.
```tsx showLineNumbers title="app/payments/data-table.tsx"
'use client'
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
})
return (
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)
}
```
<Callout>
**Tip**: If you find yourself using `<DataTable />` in multiple places, this is the component you could make reusable by extracting it to `components/ui/data-table.tsx`.
`<DataTable columns={columns} data={data} />`
</Callout>
### Render the table
Finally, we'll render our table in our page component.
```tsx showLineNumbers title="app/payments/page.tsx" {22}
import { Payment, columns } from './columns'
import { DataTable } from './data-table'
async function getData(): Promise<Payment[]> {
// Fetch data from your API here.
return [
{
id: '728ed52f',
amount: 100,
status: 'pending',
email: 'm@example.com',
},
// ...
]
}
export default async function DemoPage() {
const data = await getData()
return (
<div className="container mx-auto py-10">
<DataTable columns={columns} data={data} />
</div>
)
}
```
</Steps>
## Cell Formatting
Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.
<Steps>
### Update columns definition
Update the `header` and `cell` definitions for amount as follows:
```tsx showLineNumbers title="app/payments/columns.tsx" {4-15}
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'amount',
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
return <div className="text-right font-medium">{formatted}</div>
},
},
]
```
You can use the same approach to format other cells and headers.
</Steps>
## Row Actions
Let's add row actions to our table. We'll use a `<Dropdown />` component for this.
<Steps>
### Update columns definition
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
```tsx showLineNumbers title="app/payments/columns.tsx" {4,6-14,18-45}
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { MoreHorizontal } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
export const columns: ColumnDef<Payment>[] = [
// ...
{
id: 'actions',
cell: ({ row }) => {
const payment = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(payment.id)}>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
},
// ...
]
```
You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.
</Steps>
## Pagination
Next, we'll add pagination to our table.
<Steps>
### Update `<DataTable>`
```tsx showLineNumbers title="app/payments/data-table.tsx" {5,17}
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from '@tanstack/react-table'
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
// ...
}
```
This will automatically paginate your rows into pages of 10. See the [pagination docs](https://tanstack.com/table/v8/docs/api/features/pagination) for more information on customizing page size and implementing manual pagination.
### Add pagination controls
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
```tsx showLineNumbers title="app/payments/data-table.tsx" {1,15,21-39}
import { Button } from "@/components/ui/button"
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
return (
<div>
<div className="rounded-md border">
<Table>
{ // .... }
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}
```
See [Reusable Components](#reusable-components) section for a more advanced pagination component.
</Steps>
## Sorting
Let's make the email column sortable.
<Steps>
### Update `<DataTable>`
```tsx showLineNumbers title="app/payments/data-table.tsx" showLineNumbers {3,6,10,18,25-28}
"use client"
import * as React from "react"
import {
ColumnDef,
SortingState,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
})
return (
<div>
<div className="rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
)
}
```
### Make header cell sortable
We can now update the `email` header cell to add sorting controls.
```tsx showLineNumbers title="app/payments/columns.tsx" {4,9-19}
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { ArrowUpDown, MoreHorizontal } from 'lucide-react'
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'email',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Email
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
]
```
This will automatically sort the table (asc and desc) when the user toggles on the header cell.
</Steps>
## Filtering
Let's add a search input to filter emails in our table.
<Steps>
### Update `<DataTable>`
```tsx showLineNumbers title="app/payments/data-table.tsx" {6,10,17,24-26,35-36,39,45-54}
"use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
},
})
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<div className="rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
)
}
```
Filtering is now enabled for the `email` column. You can add filters to other columns as well. See the [filtering docs](https://tanstack.com/table/v8/docs/guide/filters) for more information on customizing filters.
</Steps>
## Visibility
Adding column visibility is fairly simple using `@tanstack/react-table` visibility API.
<Steps>
### Update `<DataTable>`
```tsx showLineNumbers title="app/payments/data-table.tsx" {8,18-23,33-34,45,49,64-91}
"use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
state: {
sorting,
columnFilters,
columnVisibility,
},
})
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder="Filter emails..."
value={table.getColumn("email")?.getFilterValue() as string}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter(
(column) => column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
)
}
```
This adds a dropdown menu that you can use to toggle column visibility.
</Steps>
## Row Selection
Next, we're going to add row selection to our table.
<Steps>
### Update column definitions
```tsx showLineNumbers title="app/payments/columns.tsx" {6,9-27}
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { Badge } from '@/components/ui/badge'
import { Checkbox } from '@/components/ui/checkbox'
export const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
]
```
### Update `<DataTable>`
```tsx showLineNumbers title="app/payments/data-table.tsx" {11,23,28}
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
const [rowSelection, setRowSelection] = React.useState({})
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div>
<div className="rounded-md border">
<Table />
</div>
</div>
)
}
```
This adds a checkbox to each row and a checkbox in the header to select all rows.
### Show selected rows
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
```tsx
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length}{' '}
row(s) selected.
</div>
```
</Steps>
## Reusable Components
Here are some components you can use to build your data tables. This is from the [Tasks](/examples/tasks) demo.
### Column header
Make any column header sortable and hideable.
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-column-header.tsx" />
```tsx {5}
export const columns = [
{
accessorKey: 'email',
header: ({ column }) => <DataTableColumnHeader column={column} title="Email" />,
},
]
```
### Pagination
Add pagination controls to your table including page size and selection count.
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-pagination.tsx" />
```tsx
<DataTablePagination table={table} />
```
### Column toggle
A component to toggle column visibility.
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-view-options.tsx" />
```tsx
<DataTableViewOptions table={table} />
```

View File

@@ -0,0 +1,74 @@
---
title: Date Picker
description: A date picker component with range and presets.
component: true
source:
shadcn: true
---
<ComponentPreview name="date-picker-demo" peekCode wide />
## Installation
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
## Usage
```tsx
'use client'
import * as React from 'react'
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
export function DatePickerDemo() {
const [date, setDate] = React.useState<Date>()
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={'outline'}
className={cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground'
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, 'PPP') : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
</PopoverContent>
</Popover>
)
}
```
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
## Examples
### Date Picker
<ComponentPreview name="date-picker-demo" peekCode wide />
### Date Range Picker
<ComponentPreview name="date-picker-with-range" />
### With Presets
<ComponentPreview name="date-picker-with-presets" />
### Form
<ComponentPreview name="date-picker-form" />

View File

@@ -0,0 +1,120 @@
---
title: Dialog
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
featured: true
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/dialog
api: https://www.radix-ui.com/docs/primitives/components/dialog#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="dialog-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add dialog
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-dialog
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="dialog" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
```
```tsx
<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
```
## Examples
### Custom close button
<ComponentPreview name="dialog-close-button" />
## Notes
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or
`Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
```tsx {14-25}
<Dialog>
<ContextMenu>
<ContextMenuTrigger>Right click</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Open</ContextMenuItem>
<ContextMenuItem>Download</ContextMenuItem>
<DialogTrigger asChild>
<ContextMenuItem>
<span>Delete</span>
</ContextMenuItem>
</DialogTrigger>
</ContextMenuContent>
</ContextMenu>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. Are you sure you want to permanently delete this file from our
servers?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="submit">Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
```

View File

@@ -0,0 +1,95 @@
---
title: Drawer
description: A drawer component for React.
component: true
links:
doc: https://github.com/emilkowalski/vaul
source:
vaul: true
shadcn: true
---
<ComponentPreview name="drawer-demo" peekCode wide />
## About
Drawer is built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski\_](https://twitter.com/emilkowalski_).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add drawer
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install vaul
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="drawer" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx showLineNumbers
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer'
```
```tsx showLineNumbers
<Drawer>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
```
## Examples
### Responsive Dialog
You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.
<ComponentPreview name="drawer-dialog" />

View File

@@ -0,0 +1,95 @@
---
title: Dropdown Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
featured: true
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/dropdown-menu
api: https://www.radix-ui.com/docs/primitives/components/dropdown-menu#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="dropdown-menu-demo" peekCode wide />
## Installation
So I guess I can write anything i want in here.
## Props
{/* <ComponentProps component={'/Dropdown'} foo="world" /> */}
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add dropdown-menu
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-dropdown-menu
```
<Step>Copy and paste the following code into your project.</Step>
{/* <ComponentSource name="dropdown-menu" /> */}
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
```
```tsx
<DropdownMenu>
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
```
## Examples
### Checkboxes
<ComponentPreview name="dropdown-menu-checkboxes" />
### Radio Group
<ComponentPreview name="dropdown-menu-radio-group" />

View File

@@ -0,0 +1,255 @@
---
title: React Hook Form
description: Building forms with React Hook Form and Zod.
links:
doc: https://react-hook-form.com
source:
shadcn: true
---
Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.
Well-designed HTML forms are:
- Well-structured and semantically correct.
- Easy to use and navigate (keyboard).
- Accessible with ARIA attributes and proper labels.
- Has support for client and server side validation.
- Well-styled and consistent with the rest of the application.
In this guide, we will take a look at building forms with [`react-hook-form`](https://react-hook-form.com/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix UI components.
## Features
The `<Form />` component is a wrapper around the `react-hook-form` library. It provides a few things:
- Composable components for building forms.
- A `<FormField />` component for building controlled form fields.
- Form validation using `zod`.
- Handles accessibility and error messages.
- Uses `React.useId()` for generating unique IDs.
- Applies the correct `aria` attributes to form fields based on states.
- Built to work with all Radix UI components.
- Bring your own schema library. We use `zod` but you can use anything you want.
- **You have full control over the markup and styling.**
## Anatomy
```tsx
<Form>
<FormField
control={...}
name="..."
render={() => (
<FormItem>
<FormLabel />
<FormControl>
{ /* Your form field */}
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
)}
/>
</Form>
```
## Example
```tsx
const form = useForm()
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
```
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
<Steps>
### Command
```bash
npx shadcn-ui@latest add form
```
</Steps>
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-label @radix-ui/react-slot react-hook-form @hookform/resolvers zod
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="form" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
<Steps>
### Create a form schema
Define the shape of your form using a Zod schema. You can read more about using Zod in the [Zod documentation](https://zod.dev).
```tsx showLineNumbers {3,5-7}
'use client'
import { z } from 'zod'
const formSchema = z.object({
username: z.string().min(2).max(50),
})
```
### Define a form
Use the `useForm` hook from `react-hook-form` to create a form.
```tsx showLineNumbers {3-4,14-20,22-27}
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
const formSchema = z.object({
username: z.string().min(2, {
message: 'Username must be at least 2 characters.',
}),
})
export function ProfileForm() {
// 1. Define your form.
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
},
})
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values)
}
}
```
Since `FormField` is using a controlled component, you need to provide a default value for the field. See the [React Hook Form docs](https://react-hook-form.com/docs/usecontroller) to learn more about controlled components.
### Build your form
We can now use the `<Form />` components to build our form.
```tsx showLineNumbers {7-17,28-50}
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
const formSchema = z.object({
username: z.string().min(2, {
message: 'Username must be at least 2 characters.',
}),
})
export function ProfileForm() {
// ...
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
```
### Done
That's it. You now have a fully accessible form that is type-safe with client-side validation.
<ComponentPreview
name="input-form"
className="[&_[role=tablist]]:hidden [&>div>div:first-child]:hidden"
/>
</Steps>
## Examples
See the following links for more examples on how to use the `<Form />` component with other components:
- [Checkbox](/docs/components/checkbox#form)
- [Date Picker](/docs/components/date-picker#form)
- [Input](/docs/components/input#form)
- [Radio Group](/docs/components/radio-group#form)
- [Select](/docs/components/select#form)
- [Switch](/docs/components/switch#form)
- [Textarea](/docs/components/textarea#form)
- [Combobox](/docs/components/combobox#form)

View File

@@ -0,0 +1,64 @@
---
title: Hover Card
description: For sighted users to preview content available behind a link.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/hover-card
api: https://www.radix-ui.com/docs/primitives/components/hover-card#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="hover-card-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add hover-card
```
</TabsContent>
<TabsContent value="manual">
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-hover-card
```
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="hover-card" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
```
```tsx
<HoverCard>
<HoverCardTrigger>Hover</HoverCardTrigger>
<HoverCardContent>The React Framework created and maintained by @vercel.</HoverCardContent>
</HoverCard>
```

View File

@@ -0,0 +1,191 @@
---
title: Input OTP
description: Accessible one-time password component with copy paste functionality.
component: true
links:
doc: https://input-otp.rodz.dev
source:
shadcn: true
inputOtp: true
---
<ComponentPreview name="input-otp-demo" peekCode wide />
## About
Input OTP is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme_rodz](https://twitter.com/guilherme_rodz).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
<Steps>
<Step>Run the following command:</Step>
```bash
npx shadcn-ui@latest add input-otp
```
<Step>Update `tailwind.config.js`</Step>
Add the following animations to your `tailwind.config.js` file:
```js showLineNumbers title="tailwind.config.js" {6-9,12}
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
'caret-blink': {
'0%,70%,100%': { opacity: '1' },
'20%,50%': { opacity: '0' },
},
},
animation: {
'caret-blink': 'caret-blink 1.25s ease-out infinite',
},
},
},
}
```
</Steps>
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install input-otp
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="input-otp" />
<Step>Update the import paths to match your project setup.</Step>
<Step>Update `tailwind.config.js`</Step>
Add the following animations to your `tailwind.config.js` file:
```js showLineNumbers title="tailwind.config.js" {6-9,12}
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
'caret-blink': {
'0%,70%,100%': { opacity: '1' },
'20%,50%': { opacity: '0' },
},
},
animation: {
'caret-blink': 'caret-blink 1.25s ease-out infinite',
},
},
},
}
```
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@/components/ui/input-otp'
```
```tsx
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
```
## Examples
### Pattern
Use the `pattern` prop to define a custom pattern for the OTP input.
<ComponentPreview name="input-otp-pattern" />
```tsx showLineNumbers {1,7}
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
...
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
{/* ... */}
</InputOTPGroup>
</InputOTP>
```
### Separator
You can use the `<InputOTPSeparator />` component to add a separator between the input groups.
<ComponentPreview name="input-otp-separator" />
```tsx showLineNumbers {4,15}
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
...
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
```
### Controlled
You can use the `value` and `onChange` props to control the input value.
<ComponentPreview name="input-otp-controlled" />
### Form
<ComponentPreview name="input-otp-form" />

View File

@@ -0,0 +1,77 @@
---
title: Input
description: Displays a form input field or a component that looks like an input field.
component: true
source:
shadcn: true
---
<ComponentPreview name="input-demo" className="[&_input]:max-w-xs" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add input
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="input" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Input } from '@/components/ui/input'
```
```tsx
<Input />
```
## Examples
### Default
<ComponentPreview name="input-demo" className="[&_input]:max-w-xs" />
### File
<ComponentPreview name="input-file" className="[&_input]:max-w-xs" />
### Disabled
<ComponentPreview name="input-disabled" className="[&_input]:max-w-xs" />
### With Label
<ComponentPreview name="input-with-label" className="[&_input]:max-w-xs" />
### With Button
<ComponentPreview name="input-with-button" className="[&_input]:max-w-xs" />
### Form
<ComponentPreview name="input-form" />

View File

@@ -0,0 +1,61 @@
---
title: Label
description: Renders an accessible label associated with controls.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/label
api: https://www.radix-ui.com/docs/primitives/components/label#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="label-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add label
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-label
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="label" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Label } from '@/components/ui/label'
```
```tsx
<Label htmlFor="email">Your email address</Label>
```

View File

@@ -0,0 +1,83 @@
---
title: Menubar
description: A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/menubar
api: https://www.radix-ui.com/docs/primitives/components/menubar#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="menubar-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add menubar
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-menubar
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="menubar" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from '@/components/ui/menubar'
```
```tsx
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>New Window</MenubarItem>
<MenubarSeparator />
<MenubarItem>Share</MenubarItem>
<MenubarSeparator />
<MenubarItem>Print</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
```

View File

@@ -0,0 +1,99 @@
---
title: Navigation Menu
description: A collection of links for navigating websites.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/navigation-menu
api: https://www.radix-ui.com/docs/primitives/components/navigation-menu#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="navigation-menu-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add navigation-menu
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-navigation-menu
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="navigation-menu" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
NavigationMenuViewport,
} from '@/components/ui/navigation-menu'
```
```tsx
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
```
## Examples
### Link Component
When using the Next.js `<Link />` component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
```tsx
import { navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'
```
```tsx {3-5}
<NavigationMenuItem>
<Link href="/docs" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>Documentation</NavigationMenuLink>
</Link>
</NavigationMenuItem>
```
See also the [Radix UI documentation](https://www.radix-ui.com/docs/primitives/components/navigation-menu#with-client-side-routing) for handling client side routing.

View File

@@ -0,0 +1,104 @@
---
title: Pagination
description: Pagination with page navigation, next and previous links.
component: true
source:
shadcn: true
---
<ComponentPreview name="pagination-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add pagination
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="pagination" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from '@/components/ui/pagination'
```
```tsx
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
```
### Next.js
By default the `<PaginationLink />` component will render an `<a />` tag.
To use the Next.js `<Link />` component, make the following updates to `pagination.tsx`.
```diff showLineNumbers /typeof Link/ {1}
+ import Link from "next/link"
- type PaginationLinkProps = ... & React.ComponentProps<"a">
+ type PaginationLinkProps = ... & React.ComponentProps<typeof Link>
const PaginationLink = ({...props }: ) => (
<PaginationItem>
- <a>
+ <Link>
// ...
- </a>
+ </Link>
</PaginationItem>
)
```
<Callout className="mt-6">
**Note:** We are making updates to the cli to automatically do this for you.
</Callout>

View File

@@ -0,0 +1,64 @@
---
title: Popover
description: Displays rich content in a portal, triggered by a button.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/popover
api: https://www.radix-ui.com/docs/primitives/components/popover#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="popover-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add popover
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-popover
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="popover" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
```
```tsx
<Popover>
<PopoverTrigger>Open</PopoverTrigger>
<PopoverContent>Place content for the popover here.</PopoverContent>
</Popover>
```

View File

@@ -0,0 +1,61 @@
---
title: Progress
description: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/progress
api: https://www.radix-ui.com/docs/primitives/components/progress#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="progress-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add progress
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-progress
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="progress" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Progress } from '@/components/ui/progress'
```
```tsx
<Progress value={33} />
```

View File

@@ -0,0 +1,77 @@
---
title: Radio Group
description: A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/radio-group
api: https://www.radix-ui.com/docs/primitives/components/radio-group#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="radio-group-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add radio-group
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-radio-group
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="radio-group" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Label } from '@/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
```
```tsx
<RadioGroup defaultValue="option-one">
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-one" id="option-one" />
<Label htmlFor="option-one">Option One</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-two" id="option-two" />
<Label htmlFor="option-two">Option Two</Label>
</div>
</RadioGroup>
```
## Examples
### Form
<ComponentPreview name="radio-group-form" />

View File

@@ -0,0 +1,110 @@
---
title: Resizable
description: Accessible resizable panel groups and layouts with keyboard support.
component: true
links:
doc: https://github.com/bvaughn/react-resizable-panels
api: https://github.com/bvaughn/react-resizable-panels/tree/main/packages/react-resizable-panels
source:
shadcn: true
---
<ComponentPreview name="resizable-demo" peekCode wide />
## About
The `Resizable` component is built on top of [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) by [bvaughn](https://github.com/bvaughn).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add resizable
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install react-resizable-panels
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="resizable" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
```
```tsx
<ResizablePanelGroup direction="horizontal">
<ResizablePanel>One</ResizablePanel>
<ResizableHandle />
<ResizablePanel>Two</ResizablePanel>
</ResizablePanelGroup>
```
## Examples
### Vertical
Use the `direction` prop to set the direction of the resizable panels.
<ComponentPreview name="resizable-vertical" />
```tsx showLineNumbers {9}
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
export default function Example() {
return (
<ResizablePanelGroup direction="vertical">
<ResizablePanel>One</ResizablePanel>
<ResizableHandle />
<ResizablePanel>Two</ResizablePanel>
</ResizablePanelGroup>
)
}
```
### Handle
You can set or hide the handle by using the `withHandle` prop on the `ResizableHandle` component.
<ComponentPreview name="resizable-handle" />
```tsx showLineNumbers {11}
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
export default function Example() {
return (
<ResizablePanelGroup direction="horizontal">
<ResizablePanel>One</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel>Two</ResizablePanel>
</ResizablePanelGroup>
)
}
```

View File

@@ -0,0 +1,73 @@
---
title: Scroll-area
description: Augments native scroll functionality for custom, cross-browser styling.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/scroll-area
api: https://www.radix-ui.com/docs/primitives/components/scroll-area#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="scroll-area-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add scroll-area
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-scroll-area
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="scroll-area" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { ScrollArea } from '@/components/ui/scroll-area'
```
```tsx
<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
Jokester began sneaking into the castle in the middle of the night and leaving jokes all over the
place: under the king's pillow, in his soup, even in the royal toilet. The king was furious, but
he couldn't seem to stop Jokester. And then, one day, the people of the kingdom discovered that
the jokes left by Jokester were so funny that they couldn't help but laugh. And once they started
laughing, they couldn't stop.
</ScrollArea>
```
## Examples
### Horizontal Scrolling
<ComponentPreview name="scroll-area-horizontal-demo" peekCode wide />

View File

@@ -0,0 +1,87 @@
---
title: Select
description: Displays a list of options for the user to pick from—triggered by a button.
component: true
featured: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/select
api: https://www.radix-ui.com/docs/primitives/components/select#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="select-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add select
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-select
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="select" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
```
```tsx
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
```
## Examples
### Scrollable
<ComponentPreview name="select-scrollable" />
### Form
<ComponentPreview name="select-form" />

View File

@@ -0,0 +1,59 @@
---
title: Separator
description: Visually or semantically separates content.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/separator
api: https://www.radix-ui.com/docs/primitives/components/separator#api-reference
source:
shadcn: true
---
<ComponentPreview name="separator-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add separator
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-separator
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="separator" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Separator } from '@/components/ui/separator'
```
```tsx
<Separator />
```

View File

@@ -0,0 +1,106 @@
---
title: Sheet
description: Extends the Dialog component to display content that complements the main content of the screen.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/dialog
api: https://www.radix-ui.com/docs/primitives/components/dialog#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="sheet-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add sheet
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-dialog
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="sheet" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
### Usage
```tsx
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet'
```
```tsx
<Sheet>
<SheetTrigger>Open</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Are you absolutely sure?</SheetTitle>
<SheetDescription>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
```
## Examples
### Side
Use the `side` property to `<SheetContent />` to indicate the edge of the screen where the component will appear. The values can be `top`, `right`, `bottom` or `left`.
<ComponentPreview name="sheet-side" />
### Size
You can adjust the size of the sheet using CSS classes:
```tsx {3}
<Sheet>
<SheetTrigger>Open</SheetTrigger>
<SheetContent className="w-[400px] sm:w-[540px]">
<SheetHeader>
<SheetTitle>Are you absolutely sure?</SheetTitle>
<SheetDescription>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
```

View File

@@ -0,0 +1,57 @@
---
title: Skeleton
description: Use to show a placeholder while content is loading.
component: true
source:
shadcn: true
---
<ComponentPreview name="skeleton-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add skeleton
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="skeleton" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Skeleton } from '@/components/ui/skeleton'
```
```tsx
<Skeleton className="w-[100px] h-[20px] rounded-full" />
```
## Examples
### Card
<ComponentPreview name="skeleton-card" />

View File

@@ -0,0 +1,61 @@
---
title: Slider
description: An input where the user selects a value from within a given range.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/slider
api: https://www.radix-ui.com/docs/primitives/components/slider#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="slider-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add slider
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-slider
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="slider" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Slider } from '@/components/ui/slider'
```
```tsx
<Slider defaultValue={[33]} max={100} step={1} />
```

View File

@@ -0,0 +1,103 @@
---
title: Sonner
description: An opinionated toast component for React.
component: true
links:
doc: https://sonner.emilkowal.ski
source:
shadcn: true
---
<ComponentPreview name="sonner-demo" peekCode wide />
## About
Sonner is built and maintained by [emilkowalski\_](https://twitter.com/emilkowalski_).
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
<Steps>
<Step>Run the following command:</Step>
```bash
npx shadcn-ui@latest add sonner
```
<Step>Add the Toaster component</Step>
```tsx title="app/layout.tsx" {1,9}
import { Toaster } from '@/components/ui/sonner'
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
</body>
</html>
)
}
```
</Steps>
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install sonner next-themes
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="sonner" />
<Step>Add the Toaster component</Step>
```tsx title="app/layout.tsx" {1,9}
import { Toaster } from '@/components/ui/sonner'
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
</body>
</html>
)
}
```
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { toast } from 'sonner'
```
```tsx
toast('Event has been created.')
```

View File

@@ -0,0 +1,67 @@
---
title: Switch
description: A control that allows the user to toggle between checked and not checked.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/switch
api: https://www.radix-ui.com/docs/primitives/components/switch#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="switch-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add switch
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-switch
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="switch" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Switch } from '@/components/ui/switch'
```
```tsx
<Switch />
```
## Examples
### Form
<ComponentPreview name="switch-form" />

View File

@@ -0,0 +1,85 @@
---
title: Table
description: A responsive table component.
component: true
source:
shadcn: true
---
<ComponentPreview name="table-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add table
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="table" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
```
```tsx
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell>Credit Card</TableCell>
<TableCell className="text-right">$250.00</TableCell>
</TableRow>
</TableBody>
</Table>
```
## Data Table
You can use the `<Table />` component to build more complex data tables. Combine it with [@tanstack/react-table](https://tanstack.com/table/v8) to create tables with sorting, filtering and pagination.
See the [Data Table](/docs/components/data-table) documentation for more information.
You can also see an example of a data table in the [Tasks](/examples/tasks) demo.

View File

@@ -0,0 +1,68 @@
---
title: Tabs
description: A set of layered sections of content—known as tab panels—that are displayed one at a time.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/tabs
api: https://www.radix-ui.com/docs/primitives/components/tabs#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="tabs-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add tabs
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-tabs
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="tabs" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
```
```tsx
<Tabs defaultValue="account" className="w-[400px]">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Make changes to your account here.</TabsContent>
<TabsContent value="password">Change your password here.</TabsContent>
</Tabs>
```

View File

@@ -0,0 +1,77 @@
---
title: Textarea
description: Displays a form textarea or a component that looks like a textarea.
component: true
source:
shadcn: true
---
<ComponentPreview name="textarea-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add textarea
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="textarea" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Textarea } from '@/components/ui/textarea'
```
```tsx
<Textarea />
```
## Examples
### Default
<ComponentPreview name="textarea-demo" peekCode wide />
### Disabled
<ComponentPreview name="textarea-disabled" />
### With Label
<ComponentPreview name="textarea-with-label" className="[&_div.grid]:w-full" />
### With Text
<ComponentPreview name="textarea-with-text" />
### With Button
<ComponentPreview name="textarea-with-button" />
### Form
<ComponentPreview name="textarea-form" />

View File

@@ -0,0 +1,155 @@
---
title: Toast
description: A succinct message that is displayed temporarily.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/toast
api: https://www.radix-ui.com/docs/primitives/components/toast#api-reference
source:
shadcn: true
---
<ComponentPreview name="toast-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
<Steps>
<Step>Run the following command:</Step>
```bash
npx shadcn-ui@latest add toast
```
<Step>Add the Toaster component</Step>
```tsx title="app/layout.tsx" {1,9}
import { Toaster } from '@/components/ui/toaster'
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
</body>
</html>
)
}
```
</Steps>
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-toast
```
<Step>Copy and paste the following code into your project.</Step>
`toast.tsx`
<ComponentSource name="toast" />
`toaster.tsx`
<ComponentSource name="toast" fileName="toaster" />
`use-toast.tsx`
<ComponentSource name="toast" fileName="use-toast" />
<Step>Update the import paths to match your project setup.</Step>
<Step>Add the Toaster component</Step>
```tsx title="app/layout.tsx" {1,9}
import { Toaster } from '@/components/ui/toaster'
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
</body>
</html>
)
}
```
</Steps>
</TabsContent>
</Tabs>
## Usage
The `useToast` hook returns a `toast` function that you can use to display a toast.
```tsx
import { useToast } from '@/components/ui/use-toast'
```
```tsx {2,7-10}
export const ToastDemo = () => {
const { toast } = useToast()
return (
<Button
onClick={() => {
toast({
title: 'Scheduled: Catch up',
description: 'Friday, February 10, 2023 at 5:57 PM',
})
}}
>
Show Toast
</Button>
)
}
```
<Callout>
To display multiple toasts at the same time, you can update the `TOAST_LIMIT` in `use-toast.tsx`.
</Callout>
## Examples
### Simple
<ComponentPreview name="toast-simple" />
### With title
<ComponentPreview name="toast-with-title" />
### With Action
<ComponentPreview name="toast-with-action" />
### Destructive
Use `toast({ variant: "destructive" })` to display a destructive toast.
<ComponentPreview name="toast-destructive" />

View File

@@ -0,0 +1,91 @@
---
title: Toggle Group
description: A set of two-state buttons that can be toggled on or off.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/toggle-group
api: https://www.radix-ui.com/docs/primitives/components/toggle-group#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="toggle-group-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add toggle-group
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-toggle-group
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="toggle-group" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
```
```tsx
<ToggleGroup type="single">
<ToggleGroupItem value="a">A</ToggleGroupItem>
<ToggleGroupItem value="b">B</ToggleGroupItem>
<ToggleGroupItem value="c">C</ToggleGroupItem>
</ToggleGroup>
```
## Examples
### Default
<ComponentPreview name="toggle-group-demo" peekCode wide />
### Outline
<ComponentPreview name="toggle-group-outline" />
### Single
<ComponentPreview name="toggle-group-single" />
### Small
<ComponentPreview name="toggle-group-sm" />
### Large
<ComponentPreview name="toggle-group-lg" />
### Disabled
<ComponentPreview name="toggle-group-disabled" />

View File

@@ -0,0 +1,87 @@
---
title: Toggle
description: A two-state button that can be either on or off.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/toggle
api: https://www.radix-ui.com/docs/primitives/components/toggle#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="toggle-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add toggle
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-toggle
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="toggle" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Toggle } from '@/components/ui/toggle'
```
```tsx
<Toggle>Toggle</Toggle>
```
## Examples
### Default
<ComponentPreview name="toggle-demo" peekCode wide />
### Outline
<ComponentPreview name="toggle-outline" />
### With Text
<ComponentPreview name="toggle-with-text" />
### Small
<ComponentPreview name="toggle-sm" />
### Large
<ComponentPreview name="toggle-lg" />
### Disabled
<ComponentPreview name="toggle-disabled" />

View File

@@ -0,0 +1,68 @@
---
title: Tooltip
description: A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
component: true
links:
doc: https://www.radix-ui.com/docs/primitives/components/tooltip
api: https://www.radix-ui.com/docs/primitives/components/tooltip#api-reference
source:
radix: true
shadcn: true
---
<ComponentPreview name="tooltip-demo" peekCode wide />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add tooltip
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @radix-ui/react-tooltip
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="tooltip" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
```
```tsx
<TooltipProvider>
<Tooltip>
<TooltipTrigger>Hover</TooltipTrigger>
<TooltipContent>
<p>Add to library</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
```

View File

@@ -0,0 +1,61 @@
---
title: Typography
description: Styles for headings, paragraphs, lists...etc
component: true
source:
shadcn: true
---
<ComponentPreview name="typography-demo" className="[&>div.min-h-[350px]]:p-6" peekCode wide />
## h1
<ComponentPreview name="typography-h1" extractClassName />
## h2
<ComponentPreview name="typography-h2" />
## h3
<ComponentPreview name="typography-h3" />
## h4
<ComponentPreview name="typography-h4" />
## p
<ComponentPreview name="typography-p" />
## blockquote
<ComponentPreview name="typography-blockquote" />
## table
<ComponentPreview name="typography-table" />
## list
<ComponentPreview name="typography-list" />
## Inline code
<ComponentPreview name="typography-inline-code" />
## Lead
<ComponentPreview name="typography-lead" />
## Large
<ComponentPreview name="typography-large" />
## Small
<ComponentPreview name="typography-small" />
## Muted
<ComponentPreview name="typography-muted" />

View File

@@ -0,0 +1,28 @@
---
title: Figma
description: Every component recreated in Figma. With customizable props, typography and icons.
---
The Figma UI Kit is open sourced by [Pietro Schirano](https://twitter.com/skirano).
<AspectRatio ratio={12 / 9} className="w-full mt-4">
<iframe
src="https://www.figma.com/embed?embed_host=share&url=https%3A%2F%2Fwww.figma.com%2Fdesign%2FWCja3lpEj1DeunV1d5zu5D%2FDesign-System%3Fnode-id%3D520%253A1715%26t%3Dz8Quu7PADvSqD0Ss-1"
className="h-full w-full overflow-hidden rounded-lg border bg-surface-100"
></iframe>
</AspectRatio>
## Additional resources
### Figma Diagram Kit
<AspectRatio ratio={12 / 9} className="w-full mt-4">
<iframe
src="https://www.figma.com/embed?embed_host=share&url=https%3A%2F%2Fwww.figma.com%2Fdesign%2Fd6EV8a9G2yLht01AayAkmJ%2FDiagram-Kit-(Variables)%3Fnode-id%3D47%253A7265%26t%3D3JsjisJgJqD08wGG-1"
className="h-full w-full overflow-hidden rounded-lg border bg-surface-100"
></iframe>
</AspectRatio>
## Grab a copy
https://www.figma.com/community/file/1203061493325953101

View File

@@ -0,0 +1,88 @@
---
title: Form Item Layout
description: A helper component that provides a layout for form items.
component: true
fragment: true
---
<ComponentPreview
name="form-item-layout-demo"
description="A helper component that provides a layout for form items."
peekCode
showDottedGrid
wide
/>
## Usage
This component is to help with the layout of form items. It automatically provides `FormItem`, `FormLabel`, `FormMessage` and `FormDescription`.
The styling and layout of these components can be customized by passing in the components as props.
The components that can be replaced in `react-hook-form` atoms are highlighted below:
```tsx showLineNumbers {1-2,6-8}
<FormItem_Shadcn_>
<FormLabel_Shadcn_>Username</FormLabel_Shadcn_>
<FormControl_Shadcn_>
<Input placeholder="shadcn" {...field} />
</FormControl_Shadcn_>
<FormDescription_Shadcn_>This is your public display name.</FormDescription_Shadcn_>
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
```
Using `FormItemLayout` it can look like this:
```tsx
<FormItemLayout label="Username" description="This is your public display name">
<FormControl_Shadcn_>
<Input placeholder="mildtomato" {...field} />
</FormControl_Shadcn_>
</FormItemLayout>
```
Please note that you must still use `FormControl_Shadcn_` to wrap input fields.
## Examples
## With Select component
<ComponentPreview
name="form-item-layout-with-select"
description="A helper component that provides a layout for form items."
showDottedGrid
/>
## Horizontal layout
This is useful for forms inside a `<SidePanel/>` / `<Sheet/>` component.
<ComponentPreview
name="form-item-layout-with-horizontal"
description="A helper component that provides a layout for form items."
showDottedGrid
/>
## With Switch component
<ComponentPreview
name="form-item-layout-with-switch"
description="A helper component that provides a layout for form items."
showDottedGrid
/>
## With Checkbox component
<ComponentPreview
name="form-item-layout-with-checkbox"
description="A helper component that provides a layout for form items."
showDottedGrid
/>
## List of items as Checkboxes
<ComponentPreview
name="form-item-layout-with-checkbox-list"
description="A helper component that provides a layout for form items."
showDottedGrid
/>

View File

@@ -0,0 +1,57 @@
---
title: Text Confirm Dialog
description: A modal dialog that interrupts the user with important content and expects a response.
component: true
---
<ComponentPreview
name="text-confirm-dialog-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.'."
peekCode
showDottedGrid
wide
/>
## Examples
### With Info Alert
<ComponentPreview
name="text-confirm-dialog-with-info-alert"
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.'."
/>
### With warning Alert
<ComponentPreview
name="text-confirm-dialog-with-warning-alert"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>
### With destructive Alert
<ComponentPreview
name="text-confirm-dialog-with-destructive-alert"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>
### With cancel button
<ComponentPreview
name="text-confirm-dialog-with-cancel-button"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>
### With children
<ComponentPreview
name="text-confirm-dialog-with-children"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>
### With size
<ComponentPreview
name="text-confirm-dialog-with-size"
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
/>

View File

@@ -0,0 +1,6 @@
---
title: Icons
description: Icons system breakdown. Copy values of Icons.
---
<Icons />

View File

@@ -0,0 +1,24 @@
---
title: Introduction
description: Components and patterns that you can copy and paste into Supabase apps. Accessible. Customizable. Open Source.
---
## FAQ
<Accordion type="multiple">
<AccordionItem value="faq-1">
<AccordionTrigger>
Why copy/paste and not packaged as a dependency?
</AccordionTrigger>
<AccordionContent>
The idea behind this is to give you ownership and control over the code, allowing you to decide how the components are built and styled.
Start with some sensible defaults, then customize the components to your needs.
One of the drawback of packaging the components in an npm package is that the style is coupled with the implementation. _The design of your components should be separate from their implementation._
</AccordionContent>
</AccordionItem>
</Accordion>

Some files were not shown because too many files have changed in this diff Show More