feat: initial commit

This commit is contained in:
shadcn
2023-01-24 19:51:29 +04:00
commit 0a241be17f
254 changed files with 23635 additions and 0 deletions

3
.commitlintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

10
.editorconfig Normal file
View File

@@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

24
.eslintrc.json Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/eslintrc",
"root": true,
"extends": [
"next/core-web-vitals",
"turbo",
"prettier",
"plugin:tailwindcss/recommended"
],
"plugins": ["tailwindcss"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
"tailwindcss/no-custom-classname": "off"
},
"settings": {
"tailwindcss": {
"callees": ["cn"]
},
"next": {
"rootDir": ["apps/*/"]
}
}
}

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
.next/
out/
build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# turbo
.turbo
.contentlayer

4
.husky/commit-msg Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx commitlint --edit $1

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
auto-install-peers=true

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.18.0

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"eslint.workingDirectories": [
{ "pattern": "apps/*/" },
{ "pattern": "packages/*/" }
]
}

46
README.md Normal file
View File

@@ -0,0 +1,46 @@
# shadcn/ui
Beautifully designed components built with Radix UI and Tailwind CSS.
![hero](apps/www/public/og.jpg)
## Roadmap
> **Warning**
> This is work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn).
- [ ] Toast
- [ ] Toggle
- [ ] Toggle Group
- [ ] Toolbar
- [ ] Navigation Menu
- [ ] Figma?
## Get Started
Starting a new project? Check out the Next.js template.
```bash
npx create-next-app -e https://github.com/shadcn/next-template
```
### Features
- Radix UI Primitives
- Tailwind CSS
- Fonts with `@next/font`
- Icons from [Lucide](https://lucide.dev)
- Dark mode with `next-themes`
- Automatic import sorting with `@ianvs/prettier-plugin-sort-imports`
### Tailwind CSS Features
- Class merging with `taiwind-merge`
- Animation with `tailwindcss-animate`
- Conditional classes with `clsx`
- Variants with `class-variance-authority`
- Automatic class sorting with `eslint-plugin-tailwindcss`
## License
Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).

4
apps/www/.env.example Normal file
View File

@@ -0,0 +1,4 @@
# -----------------------------------------------------------------------------
# App
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_URL=http://localhost:3000

2
apps/www/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
.env

View File

@@ -0,0 +1,25 @@
import { allDocs } from "contentlayer/generated"
import MdxHead from "@/components/mdx-head"
interface HeadProps {
params: {
slug: string[]
}
}
export default function Head({ params }: HeadProps) {
const slug = params?.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
return null
}
return (
<MdxHead
params={params}
og={{ heading: doc.title, type: doc.title, mode: "light" }}
/>
)
}

View File

@@ -0,0 +1,80 @@
import { notFound } from "next/navigation"
import { allDocs } from "contentlayer/generated"
import "@/styles/mdx.css"
import Link from "next/link"
import { getTableOfContents } from "@/lib/toc"
import { Icons } from "@/components/icons"
import { Mdx } from "@/components/mdx"
import { DocsPageHeader } from "@/components/page-header"
import { DocsPager } from "@/components/pager"
import { DashboardTableOfContents } from "@/components/toc"
import { Separator } from "@/components/ui/separator"
interface DocPageProps {
params: {
slug: string[]
}
}
export async function generateStaticParams(): Promise<
DocPageProps["params"][]
> {
return allDocs.map((doc) => ({
slug: doc.slugAsParams.split("/"),
}))
}
export default async function DocPage({ params }: DocPageProps) {
const slug = params?.slug?.join("/") || ""
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
if (!doc) {
notFound()
}
const toc = await getTableOfContents(doc.body.raw)
return (
<main className="relative py-6 lg:gap-10 lg:py-10 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0">
<DocsPageHeader heading={doc.title} text={doc.description}>
{doc.radix ? (
<div className="flex items-center space-x-2 pt-4">
{doc.radix?.link && (
<Link
href={doc.radix.link}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50"
>
<Icons.radix className="mr-1 h-3 w-3" />
Radix UI
</Link>
)}
{doc.radix?.api && (
<Link
href={doc.radix.api}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50"
>
API Reference
</Link>
)}
</div>
) : null}
</DocsPageHeader>
<Mdx code={doc.body.code} />
<Separator className="my-4 md:my-6" />
<DocsPager doc={doc} />
</div>
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
<DashboardTableOfContents toc={toc} />
</div>
</div>
</main>
)
}

View File

@@ -0,0 +1,20 @@
import { docsConfig } from "@/config/docs"
import { DocsSidebarNav } from "@/components/sidebar-nav"
import { ScrollArea } from "@/components/ui/scroll-area"
interface DocsLayoutProps {
children: React.ReactNode
}
export default function DocsLayout({ children }: DocsLayoutProps) {
return (
<div className="flex-1 items-start md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10">
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto border-r border-r-slate-100 dark:border-r-slate-700 md:sticky md:block">
<ScrollArea className="pr-6 lg:py-10">
<DocsSidebarNav items={docsConfig.sidebarNav} />
</ScrollArea>
</aside>
{children}
</div>
)
}

43
apps/www/app/head.tsx Normal file
View File

@@ -0,0 +1,43 @@
import { siteConfig } from "@/config/site"
export default function Head() {
const url = process.env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/og.jpg`)
return (
<>
<title>{`${siteConfig.name} - ${siteConfig.description}`}</title>
<meta charSet="utf-8" />
<meta name="description" content={siteConfig.description} />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta property="og:type" content="website" />
<meta property="og:title" content={siteConfig.name} />
<meta property="og:description" content={siteConfig.description} />
<meta property="og:url" content={url?.toString()} />
<meta property="og:image" content={ogUrl.toString()} />
<meta name="twitter:title" content={siteConfig.name} />
<meta name="twitter:description" content={siteConfig.description} />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url?.toString()} />
<meta name="twitter:image" content={ogUrl.toString()} />
</>
)
}

44
apps/www/app/layout.tsx Normal file
View File

@@ -0,0 +1,44 @@
import { Inter as FontSans } from "@next/font/google"
import "@/styles/globals.css"
import { cn } from "@/lib/utils"
import { Analytics } from "@/components/analytics"
import { SiteFooter } from "@/components/site-footer"
import { SiteHeader } from "@/components/site-header"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider"
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
})
interface RootLayoutProps {
children: React.ReactNode
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<>
<html lang="en" suppressHydrationWarning>
<head />
<body
className={cn(
"min-h-screen bg-white font-sans text-slate-900 antialiased dark:bg-slate-900 dark:text-slate-50",
fontSans.variable
)}
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="flex min-h-screen flex-col">
<SiteHeader />
<div className="container flex-1">{children}</div>
<SiteFooter />
</div>
<TailwindIndicator />
</ThemeProvider>
</body>
</html>
<Analytics />
</>
)
}

71
apps/www/app/page.tsx Normal file
View File

@@ -0,0 +1,71 @@
import Link from "next/link"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { AppleMusicDemo } from "@/components/apple-music-demo"
import { CopyButton } from "@/components/copy-button"
import { PromoVideo } from "@/components/promo-video"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { buttonVariants } from "@/components/ui/button"
export default function IndexPage() {
return (
<>
<section className="grid items-center gap-6 pt-6 pb-8 md:py-10">
<div className="flex max-w-[980px] flex-col items-start gap-2">
<h1 className="text-3xl font-extrabold leading-tight tracking-tighter md:text-5xl lg:text-6xl lg:leading-[1.1]">
Beautifully designed components <br className="hidden sm:inline" />
built with Radix UI and Tailwind CSS.
</h1>
<p className="max-w-[700px] text-lg text-slate-700 dark:text-slate-400 sm:text-xl">
Accessible and customizable components that you can copy and paste
into your apps. Free. Open Source. And Next.js 13 Ready.
</p>
</div>
<div className="block lg:hidden">
<PromoVideo />
</div>
<div className="flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4 md:flex-row">
<Link href="/docs" className={buttonVariants({ size: "lg" })}>
Documentation
</Link>
<Link
target="_blank"
rel="noreferrer"
href={siteConfig.links.github}
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"md:hidden"
)}
>
GitHub
</Link>
<pre className="hidden h-11 items-center justify-between space-x-2 overflow-x-auto rounded-lg border border-slate-100 bg-slate-100 pr-2 pl-6 dark:border-slate-700 dark:bg-black md:flex">
<code className="font-mono text-sm font-semibold text-slate-900 dark:text-slate-50">
npx create-next-app -e https://github.com/shadcn/next-template
</code>
<CopyButton
value="npx create-next-app -e https://github.com/shadcn/next-template"
className="border-none text-slate-900 hover:bg-transparent dark:text-slate-50"
/>
</pre>
</div>
<div>
<p className="text-sm text-slate-500 dark:text-slate-400">
You are looking at an early preview. You can follow the progress on{" "}
<Link
href={siteConfig.links.twitter}
className="font-medium underline underline-offset-4"
>
Twitter
</Link>
.
</p>
</div>
</section>
<section className="hidden lg:block">
<AppleMusicDemo />
</section>
</>
)
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
"use client"
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
export function Analytics() {
return <VercelAnalytics />
}

View File

@@ -0,0 +1,745 @@
import * as React from "react"
import Image from "next/image"
import {
Album,
CreditCard,
Globe,
Keyboard,
LayoutGrid,
Library,
ListMusic,
LogOut,
Mail,
MessageSquare,
Mic,
Mic2,
Music,
Music2,
PlayCircle,
Plus,
PlusCircle,
Podcast,
Radio,
Settings,
User,
UserPlus,
Users,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
const playlists = [
"Recently Added",
"Recently Played",
"Top Songs",
"Top Albums",
"Top Artists",
"Logic Discography",
"Bedtime Beats",
"Feeling Happy",
"I miss Y2K Pop",
"Runtober",
"Mellow Days",
"Eminem Essentials",
]
interface Album {
name: string
artist: string
cover: string
}
const listenNowAlbums: Album[] = [
{
name: "Async Awakenings",
artist: "Nina Netcode",
cover:
"https://images.unsplash.com/photo-1547355253-ff0740f6e8c1?w=300&dpr=2&q=80",
},
{
name: "The Art of Reusability",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1576075796033-848c2a5f3696?w=300&dpr=2&q=80",
},
{
name: "Stateful Symphony",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1606542758304-820b04394ac2?w=300&dpr=2&q=80",
},
{
name: "React Rendezvous",
artist: "Ethan Byte",
cover:
"https://images.unsplash.com/photo-1598295893369-1918ffaf89a2?w=300&dpr=2&q=80",
},
]
const madeForYouAlbums: Album[] = [
{
name: "Async Awakenings",
artist: "Nina Netcode",
cover:
"https://images.unsplash.com/photo-1580428180098-24b353d7e9d9?w=300&dpr=2&q=80",
},
{
name: "Stateful Symphony",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1606542758304-820b04394ac2?w=300&dpr=2&q=80",
},
{
name: "Stateful Symphony",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1598062548091-a6fb6a052562?w=300&dpr=2&q=80",
},
{
name: "The Art of Reusability",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1626759486966-c067e3f79982?w=300&dpr=2&q=80",
},
{
name: "Thinking Components",
artist: "Lena Logic",
cover:
"https://images.unsplash.com/photo-1576075796033-848c2a5f3696?w=300&dpr=2&q=80",
},
{
name: "Functional Fury",
artist: "Beth Binary",
cover:
"https://images.unsplash.com/photo-1606542758304-820b04394ac2?w=300&dpr=2&q=80",
},
{
name: "React Rendezvous",
artist: "Ethan Byte",
cover:
"https://images.unsplash.com/photo-1598295893369-1918ffaf89a2?w=300&dpr=2&q=80",
},
]
export function AppleMusicDemo() {
return (
<div className="overflow-hidden rounded-md border border-slate-200 bg-gradient-to-b from-rose-500 to-indigo-700 shadow-2xl dark:border-slate-800">
<Menubar className="rounded-none border-b border-none dark:bg-slate-900">
<MenubarMenu>
<MenubarTrigger className="font-bold">Music</MenubarTrigger>
<MenubarContent>
<MenubarItem>About Music</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Preferences... <MenubarShortcut>,</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Hide Music... <MenubarShortcut>H</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Hide Others... <MenubarShortcut>H</MenubarShortcut>
</MenubarItem>
<MenubarShortcut />
<MenubarItem>
Quit Music <MenubarShortcut>Q</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger className="relative">
File
<DemoIndicator />
</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>New</MenubarSubTrigger>
<MenubarSubContent className="w-[230px]">
<MenubarItem>
Playlist <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Playlist from Selection <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Smart Playlist... <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem>Playlist Folder</MenubarItem>
<MenubarItem disabled>Genius Playlist</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarItem>
Open Stream URL... <MenubarShortcut>U</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Close Window <MenubarShortcut>W</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Library</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Update Cloud Library</MenubarItem>
<MenubarItem>Update Genius</MenubarItem>
<MenubarSeparator />
<MenubarItem>Organize Library...</MenubarItem>
<MenubarItem>Export Library...</MenubarItem>
<MenubarSeparator />
<MenubarItem>Import Playlist...</MenubarItem>
<MenubarItem disabled>Export Playlist...</MenubarItem>
<MenubarItem>Show Duplicate Items</MenubarItem>
<MenubarSeparator />
<MenubarItem>Get Album Artwork</MenubarItem>
<MenubarItem disabled>Get Track Names</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarItem>
Import... <MenubarShortcut>O</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>Burn Playlist to Disc...</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Show in Finder <MenubarShortcut>R</MenubarShortcut>{" "}
</MenubarItem>
<MenubarItem>Convert</MenubarItem>
<MenubarSeparator />
<MenubarItem>Page Setup...</MenubarItem>
<MenubarItem disabled>
Print... <MenubarShortcut>P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem disabled>
Undo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Redo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem disabled>
Cut <MenubarShortcut>X</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Copy <MenubarShortcut>C</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Paste <MenubarShortcut>V</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Select All <MenubarShortcut>A</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>
Deselect All <MenubarShortcut>A</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Smart Dictation...{" "}
<MenubarShortcut>
<Mic className="h-4 w-4" />
</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Emoji & Symbols{" "}
<MenubarShortcut>
<Globe className="h-4 w-4" />
</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Show Playing Next</MenubarCheckboxItem>
<MenubarCheckboxItem checked>Show Lyrics</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset disabled>
Show Status Bar
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem>
<MenubarItem disabled inset>
Enter Full Screen
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Account</MenubarTrigger>
<MenubarContent forceMount>
<MenubarLabel inset>Switch Account</MenubarLabel>
<MenubarSeparator />
<MenubarRadioGroup value="benoit">
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Manage Famliy...</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Add Account...</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
<div className="p-8">
<div className="rounded-md bg-white shadow-2xl transition-all dark:bg-slate-900">
<div className="grid grid-cols-4 xl:grid-cols-5">
<aside className="pb-12">
<div className="px-8 py-6">
<p className="flex items-center text-2xl font-semibold tracking-tight">
<Music className="mr-2" />
Music
</p>
</div>
<div className="space-y-4">
<div className="px-6 py-2">
<h2 className="mb-2 px-2 text-lg font-semibold tracking-tight">
Discover
</h2>
<div className="space-y-1">
<Button
variant="subtle"
size="sm"
className="w-full justify-start"
>
<PlayCircle className="mr-2 h-4 w-4" />
Listen Now
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<LayoutGrid className="mr-2 h-4 w-4" />
Browse
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<Radio className="mr-2 h-4 w-4" />
Radio
</Button>
</div>
</div>
<div className="px-6 py-2">
<h2 className="mb-2 px-2 text-lg font-semibold tracking-tight">
Library
</h2>
<div className="space-y-1">
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<ListMusic className="mr-2 h-4 w-4" />
Playlists
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<Music2 className="mr-2 h-4 w-4" />
Songs
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<User className="mr-2 h-4 w-4" />
Made for You
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<Mic2 className="mr-2 h-4 w-4" />
Artists
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
>
<Library className="mr-2 h-4 w-4" />
Albums
</Button>
</div>
</div>
<div className="py-2">
<h2 className="relative px-8 text-lg font-semibold tracking-tight">
Playlists <DemoIndicator className="right-28" />
</h2>
<ScrollArea className="h-[230px] px-4">
<div className="space-y-1 p-2">
{playlists.map((playlist) => (
<Button
variant="ghost"
size="sm"
className="w-full justify-start font-normal"
>
<ListMusic className="mr-2 h-4 w-4" />
{playlist}
</Button>
))}
</div>
</ScrollArea>
</div>
</div>
</aside>
<div className="col-span-3 border-l border-l-slate-200 dark:border-l-slate-700 xl:col-span-4">
<div className="h-full px-8 py-6">
<Tabs defaultValue="music" className="h-full space-y-6">
<div className="space-between flex items-center">
<TabsList>
<TabsTrigger value="music" className="relative">
Music <DemoIndicator className="right-2" />
</TabsTrigger>
<TabsTrigger value="podcasts">Podcasts</TabsTrigger>
<TabsTrigger value="live" disabled>
Live
</TabsTrigger>
</TabsList>
<div className="ml-auto mr-4">
<h3 className="text-sm font-semibold">Welcome back</h3>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="relative h-10 w-10 rounded-full"
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
<DemoIndicator className="right-0 top-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56"
align="end"
forceMount
>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
<span>Profile</span>
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard className="mr-2 h-4 w-4" />
<span>Billing</span>
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Keyboard className="mr-2 h-4 w-4" />
<span>Keyboard shortcuts</span>
<DropdownMenuShortcut>K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Users className="mr-2 h-4 w-4" />
<span>Team</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlus className="mr-2 h-4 w-4" />
<span>Invite users</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent forceMount>
<DropdownMenuItem>
<Mail className="mr-2 h-4 w-4" />
<span>Email</span>
</DropdownMenuItem>
<DropdownMenuItem>
<MessageSquare className="mr-2 h-4 w-4" />
<span>Message</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusCircle className="mr-2 h-4 w-4" />
<span>More...</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<TabsContent value="music" className="border-none p-0">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
Listen Now
</h2>
<p className="text-sm text-slate-500 dark:text-slate-400">
Top picks for you. Updated daily.
</p>
</div>
</div>
<Separator className="my-4" />
<div className="relative">
<DemoIndicator className="right-auto left-24 top-32 z-30" />
<div className="relative flex space-x-4">
{listenNowAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
className="w-[250px]"
/>
))}
</div>
</div>
<div className="mt-6 space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
Made for You
</h2>
<p className="text-sm text-slate-500 dark:text-slate-400">
Your personal playlists. Updated daily.
</p>
</div>
<Separator className="my-4" />
<div className="relative">
<DemoIndicator className="top-32 right-auto left-16 z-30" />
<ScrollArea>
<div className="flex space-x-4 pb-4">
{madeForYouAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
className="w-[150px]"
aspectRatio={1 / 1}
/>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
</TabsContent>
<TabsContent
value="podcasts"
className="h-full flex-col border-none p-0 data-[state=active]:flex"
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">
New Episodes
</h2>
<p className="text-sm text-slate-500 dark:text-slate-400">
Your favorite podcasts. Updated daily.
</p>
</div>
</div>
<Separator className="my-4" />
<div className="flex h-[450px] shrink-0 items-center justify-center rounded-md border border-dashed border-slate-200 dark:border-slate-700">
<div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
<Podcast className="h-10 w-10 text-slate-400" />
<h3 className="mt-4 text-lg font-semibold text-slate-900 dark:text-slate-50">
No episodes added
</h3>
<p className="mt-2 mb-4 text-sm text-slate-500 dark:text-slate-400">
You have not added any podcasts. Add one below.
</p>
<Dialog>
<DialogTrigger>
<Button size="sm" className="relative">
<Plus className="mr-2 h-4 w-4" />
Add Podcast
<DemoIndicator className="-top-1 -right-1 z-30" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Podcast</DialogTitle>
<DialogDescription>
Copy and paste the podcast feed URL to import.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="url">Podcast URL</Label>
<Input
id="url"
placeholder="https://example.com/feed.xml"
/>
</div>
</div>
<DialogFooter>
<Button>Import Podcast</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
interface AlbumArtworkProps extends React.HTMLAttributes<HTMLDivElement> {
album: Album
aspectRatio?: number
}
function AlbumArtwork({
album,
aspectRatio = 3 / 4,
className,
...props
}: AlbumArtworkProps) {
return (
<div className={cn("space-y-3", className)} {...props}>
<ContextMenu>
<ContextMenuTrigger>
<AspectRatio
ratio={aspectRatio}
className="overflow-hidden rounded-md"
>
<Image
src={album.cover}
alt={album.name}
fill
className="object-cover transition-all hover:scale-105"
/>
</AspectRatio>
</ContextMenuTrigger>
<ContextMenuContent className="w-40">
<ContextMenuItem>Add to Library</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>Add to Playlist</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem>
<PlusCircle className="mr-2 h-4 w-4" />
New Playlist
</ContextMenuItem>
<ContextMenuSeparator />
{playlists.map((playlist) => (
<ContextMenuItem key={playlist}>
<ListMusic className="mr-2 h-4 w-4" /> {playlist}
</ContextMenuItem>
))}
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem>Play Next</ContextMenuItem>
<ContextMenuItem>Play Later</ContextMenuItem>
<ContextMenuItem>Create Station</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Like</ContextMenuItem>
<ContextMenuItem>Share</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<div className="space-y-1 text-sm">
<h3 className="font-medium leading-none">{album.name}</h3>
<p className="text-xs text-slate-500 dark:text-slate-400">
{album.artist}
</p>
</div>
</div>
)
}
interface DemoIndicatorProps extends React.HTMLAttributes<HTMLSpanElement> {}
export function DemoIndicator({ className }: DemoIndicatorProps) {
return (
<span
className={cn(
"absolute top-1 right-0 flex h-5 w-5 animate-bounce items-center justify-center",
className
)}
>
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex h-3 w-3 rounded-full bg-sky-500" />
</span>
)
}

View File

@@ -0,0 +1,31 @@
import { cn } from "@/lib/utils"
interface CalloutProps {
icon?: string
children?: React.ReactNode
type?: "default" | "warning" | "danger"
}
export function Callout({
children,
icon,
type = "default",
...props
}: CalloutProps) {
return (
<div
className={cn(
"my-6 flex items-start rounded-md border border-b-4 border-slate-900 p-4",
{
"border-slate-900 dark:border-slate-700": type === "default",
"border-red-500": type === "danger",
"border-yellow-500": type === "warning",
}
)}
{...props}
>
{icon && <span className="mr-4 text-2xl">{icon}</span>}
<div>{children}</div>
</div>
)
}

View File

@@ -0,0 +1,38 @@
import Link from "next/link"
import { cn } from "@/lib/utils"
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
href?: string
disabled?: boolean
}
export function Card({
href,
className,
children,
disabled,
...props
}: CardProps) {
return (
<div
className={cn(
"group relative rounded-lg border border-slate-200 bg-transparent p-6 text-slate-900 shadow-md transition-shadow hover:shadow-lg dark:border-slate-700 dark:text-slate-50",
disabled && "cursor-not-allowed opacity-60",
className
)}
{...props}
>
<div className="flex flex-col justify-between space-y-4">
<div className="space-y-2 [&>p]:text-slate-600 [&>p]:dark:text-slate-300 [&>h4]:!mt-0 [&>h3]:!mt-0">
{children}
</div>
</div>
{href && (
<Link href={disabled ? "#" : href} className="absolute inset-0">
<span className="sr-only">View</span>
</Link>
)}
</div>
)
}

View File

@@ -0,0 +1,56 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
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]:max-h-[650px [&_pre]:my-0 [&_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-slate-900/30 to-slate-900/90 p-2",
isOpened ? "inset-x-0 bottom-3 h-12" : "inset-0"
)}
>
<CollapsibleTrigger asChild>
<Button variant="subtle" className="h-8 text-xs">
{isOpened ? "Collapse" : expandButtonTitle}
</Button>
</CollapsibleTrigger>
</div>
</div>
</Collapsible>
)
}

View File

@@ -0,0 +1,21 @@
import React from "react"
import { cn } from "@/lib/utils"
import { AspectRatio } from "@/components/ui/aspect-ratio"
export function ComponentCard({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<AspectRatio ratio={1 / 1} asChild>
<div
className={cn(
"flex items-center justify-center rounded-md border border-slate-200 p-8 dark:border-slate-700",
className
)}
{...props}
/>
</AspectRatio>
)
}

View File

@@ -0,0 +1,77 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { CopyButton, CopyWithClassNames } from "@/components/copy-button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface ComponentExampleProps extends React.HTMLAttributes<HTMLDivElement> {
extractClassname?: boolean
extractedClassNames?: string
}
export function ComponentExample({
children,
className,
extractClassname,
extractedClassNames,
...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 || null
}
}, [Code])
return (
<div
className={cn("group relative my-4 flex flex-col space-y-2", className)}
{...props}
>
<Tabs defaultValue="preview" className="mr-auto w-full">
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
</TabsList>
{extractedClassNames ? (
<CopyWithClassNames
value={codeString}
classNames={extractedClassNames}
className="border-none"
/>
) : (
codeString && <CopyButton value={codeString} />
)}
</div>
<TabsContent value="preview" className="p-0">
<div className="flex min-h-[350px] items-center justify-center p-10">
{Example}
</div>
</TabsContent>
<TabsContent value="code" className="border-none p-0">
<div className="flex flex-col space-y-4">
<div className="w-full rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto [&_button]:hidden">
{Code}
</div>
{Children && (
<div className="rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto [&_button]:hidden">
{Children}
</div>
)}
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

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

View File

@@ -0,0 +1,190 @@
"use client"
import * as React from "react"
import { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu"
import { NpmCommands } from "types/unist"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
value: string
src?: string
}
async function copyToClipboardWithMeta(
value: string,
meta?: Record<string, unknown>
) {
navigator.clipboard.writeText(value)
if (meta) {
await fetch("/api/log", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
event: "copy_primitive",
data: {
primitive: meta?.component,
},
}),
})
}
}
export function CopyButton({
value,
className,
src,
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
return (
<button
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md border-slate-200 p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none dark:text-slate-100 dark:hover:bg-slate-800",
className
)}
onClick={() => {
copyToClipboardWithMeta(value, {
component: src,
})
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
) : (
<Icons.copy className="h-4 w-4" />
)}
</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
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none dark:text-slate-100 dark:hover:bg-slate-800",
className
)}
{...props}
>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
) : (
<Icons.copy className="h-4 w-4" />
)}
<span className="sr-only">Copy</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => copyToClipboard(value)}>
<Icons.react className="mr-2 h-4 w-4" />
<span>Component</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyToClipboard(classNames)}>
<Icons.tailwind className="mr-2 h-4 w-4" />
<span>Classname</span>
</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) => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}, [])
return (
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none",
className
)}
{...props}
>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
) : (
<Icons.copy className="h-4 w-4" />
)}
<span className="sr-only">Copy</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => copyCommand(commands.__npmCommand__)}>
<Icons.npm className="mr-2 h-4 w-4 fill-[#CB3837]" />
<span>npm</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyCommand(commands.__yarnCommand__)}>
<Icons.yarn className="mr-2 h-4 w-4 fill-[#2C8EBB]" />
<span>yarn</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyCommand(commands.__pnpmCommand__)}>
<Icons.pnpm className="mr-2 h-4 w-4 fill-[#F69220]" />
<span>pnpm</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/eslintrc",
"rules": {
"react/no-unescaped-entities": "off"
}
}

View File

@@ -0,0 +1,32 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
export function AccordionDemo() {
return (
<Accordion type="single" collapsible className="w-[450px]">
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Is it styled?</AccordionTrigger>
<AccordionContent>
Yes. It comes with default styles that matches the other components'
aesthetic.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is it animated?</AccordionTrigger>
<AccordionContent>
Yes. It's animated by default, but you can disable it if you prefer.
</AccordionContent>
</AccordionItem>
</Accordion>
)
}

View File

@@ -0,0 +1,37 @@
"use client"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
export function AlertDialogDemo() {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Open</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure 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,18 @@
"use client"
import Image from "next/image"
import { AspectRatio } from "@/components/ui/aspect-ratio"
export function AspectRatioDemo() {
return (
<AspectRatio ratio={16 / 9} className="bg-slate-50 dark:bg-slate-800">
<Image
src="https://images.unsplash.com/photo-1576075796033-848c2a5f3696?w=800&dpr=2&q=80"
alt="Photo by Alvaro Pinot"
fill
className="rounded-md object-cover"
/>
</AspectRatio>
)
}

View File

@@ -0,0 +1,12 @@
"use client"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
export function AvatarDemo() {
return (
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
)
}

View File

@@ -0,0 +1,5 @@
import { Button } from "@/components/ui/button"
export function ButtonDemo() {
return <Button>Button</Button>
}

View File

@@ -0,0 +1,5 @@
import { Button } from "@/components/ui/button"
export function ButtonGhost() {
return <Button variant="ghost">Ghost</Button>
}

View File

@@ -0,0 +1,5 @@
import { Button } from "@/components/ui/button"
export function ButtonLink() {
return <Button variant="link">Link</Button>
}

View File

@@ -0,0 +1,12 @@
import { Loader2 } from "lucide-react"
import { Button } from "@/components/ui/button"
export function ButtonLoading() {
return (
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button>
)
}

View File

@@ -0,0 +1,5 @@
import { Button } from "@/components/ui/button"
export function ButtonOutline() {
return <Button variant="outline">Outline</Button>
}

View File

@@ -0,0 +1,5 @@
import { Button } from "@/components/ui/button"
export function ButtonSubtle() {
return <Button variant="subtle">Subtle</Button>
}

View File

@@ -0,0 +1,11 @@
import { Mail } from "lucide-react"
import { Button } from "@/components/ui/button"
export function ButtonWithIcon() {
return (
<Button>
<Mail className="mr-2 h-4 w-4" /> Login with Email
</Button>
)
}

View File

@@ -0,0 +1,17 @@
"use client"
import { Checkbox } from "@/components/ui/checkbox"
export function CheckboxDemo() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Accept terms and conditions
</label>
</div>
)
}

View File

@@ -0,0 +1,17 @@
"use client"
import { Checkbox } from "@/components/ui/checkbox"
export function CheckboxDisabled() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="terms2" disabled />
<label
htmlFor="terms2"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Accept terms and conditions
</label>
</div>
)
}

View File

@@ -0,0 +1,22 @@
"use client"
import { Checkbox } from "@/components/ui/checkbox"
export function CheckboxWithText() {
return (
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 leading-none">
<label
htmlFor="terms1"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Accept terms and conditions
</label>
<p className="text-sm text-slate-500 dark:text-slate-400">
You agree to our Terms of Service and Privacy Policy.
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,46 @@
"use client"
import * as React from "react"
import { ChevronsUpDown, Plus, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
export function CollapsibleDemo() {
const [isOpen, setIsOpen] = React.useState(false)
return (
<Collapsible
open={isOpen}
onOpenChange={setIsOpen}
className="w-[350px] space-y-2"
>
<div className="flex items-center justify-between space-x-4 px-4">
<h4 className="text-sm font-semibold">
@peduarte starred 3 repositories
</h4>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="w-9 p-0">
<ChevronsUpDown className="h-4 w-4" />
<span className="sr-only">Toggle</span>
</Button>
</CollapsibleTrigger>
</div>
<div className="rounded-md border border-slate-200 px-4 py-3 font-mono text-sm dark:border-slate-700">
@radix-ui/primitives
</div>
<CollapsibleContent className="space-y-2">
<div className="rounded-md border border-slate-200 px-4 py-3 font-mono text-sm dark:border-slate-700">
@radix-ui/colors
</div>
<div className="rounded-md border border-slate-200 px-4 py-3 font-mono text-sm dark:border-slate-700">
@stitches/react
</div>
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -0,0 +1,89 @@
"use client"
import {
Cloud,
CreditCard,
Github,
Keyboard,
LifeBuoy,
LogOut,
Mail,
MessageSquare,
Plus,
PlusCircle,
Settings,
User,
UserPlus,
Users,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export function ContextMenuDemo() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-slate-200 text-sm dark:border-slate-700">
Right click here
</ContextMenuTrigger>
<ContextMenuContent className="w-64">
<ContextMenuItem inset>
Back
<ContextMenuShortcut>[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset disabled>
Forward
<ContextMenuShortcut>]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset>
Reload
<ContextMenuShortcut>R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem>
Save Page As...
<ContextMenuShortcut>S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked>
Show Bookmarks Bar
<ContextMenuShortcut>B</ContextMenuShortcut>
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuSeparator />
<ContextMenuRadioGroup value="pedro">
<ContextMenuLabel inset>People</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuRadioItem value="pedro">
Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
}

View File

@@ -0,0 +1,49 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" value="Pedro Duarte" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="username" className="text-right">
Username
</Label>
<Input id="username" value="@peduarte" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,53 @@
"use client"
import * as React from "react"
import { DropdownMenuCheckboxItemProps } from "@radix-ui/react-dropdown-menu"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
type Checked = DropdownMenuCheckboxItemProps["checked"]
export function DropdownMenuCheckboxes() {
const [showStatusBar, setShowStatusBar] = React.useState<Checked>(true)
const [showActivityBar, setShowActivityBar] = React.useState<Checked>(false)
const [showPanel, setShowPanel] = React.useState<Checked>(false)
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showActivityBar}
onCheckedChange={setShowActivityBar}
disabled
>
Activity Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showPanel}
onCheckedChange={setShowPanel}
>
Panel
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,124 @@
"use client"
import {
Cloud,
CreditCard,
Github,
Keyboard,
LifeBuoy,
LogOut,
Mail,
MessageSquare,
Plus,
PlusCircle,
Settings,
User,
UserPlus,
Users,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function DropdownMenuDemo() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
<span>Profile</span>
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard className="mr-2 h-4 w-4" />
<span>Billing</span>
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Keyboard className="mr-2 h-4 w-4" />
<span>Keyboard shortcuts</span>
<DropdownMenuShortcut>K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Users className="mr-2 h-4 w-4" />
<span>Team</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlus className="mr-2 h-4 w-4" />
<span>Invite users</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<Mail className="mr-2 h-4 w-4" />
<span>Email</span>
</DropdownMenuItem>
<DropdownMenuItem>
<MessageSquare className="mr-2 h-4 w-4" />
<span>Message</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusCircle className="mr-2 h-4 w-4" />
<span>More...</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuItem>
<Plus className="mr-2 h-4 w-4" />
<span>New Team</span>
<DropdownMenuShortcut>+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Github className="mr-2 h-4 w-4" />
<span>GitHub</span>
</DropdownMenuItem>
<DropdownMenuItem>
<LifeBuoy className="mr-2 h-4 w-4" />
<span>Support</span>
</DropdownMenuItem>
<DropdownMenuItem disabled>
<Cloud className="mr-2 h-4 w-4" />
<span>API</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,37 @@
"use client"
import * as React from "react"
import { DropdownMenuCheckboxItemProps } from "@radix-ui/react-dropdown-menu"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function DropdownMenuRadioGroupDemo() {
const [position, setPosition] = React.useState("bottom")
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,39 @@
import { CalendarDays } from "lucide-react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
export function HoverCardDemo() {
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button variant="link">@nextjs</Button>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex justify-between space-x-4">
<Avatar>
<AvatarImage src="https://github.com/vercel.png" />
<AvatarFallback>VC</AvatarFallback>
</Avatar>
<div className="space-y-1">
<h4 className="text-sm font-semibold">@nextjs</h4>
<p className="text-sm">
The React Framework created and maintained by @vercel.
</p>
<div className="flex items-center pt-2">
<CalendarDays className="mr-2 h-4 w-4 opacity-70" />{" "}
<span className="text-xs text-slate-500 dark:text-slate-400">
Joined December 2021
</span>
</div>
</div>
</div>
</HoverCardContent>
</HoverCard>
)
}

View File

@@ -0,0 +1,117 @@
import { AccordionDemo } from "@/components/examples/accordion/demo"
import { AlertDialogDemo } from "@/components/examples/alert-dialog/demo"
import { AspectRatioDemo } from "@/components/examples/aspect-ratio/demo"
import { AvatarDemo } from "@/components/examples/avatar/demo"
import { ButtonDemo } from "@/components/examples/button/demo"
import { ButtonGhost } from "@/components/examples/button/ghost"
import { ButtonLink } from "@/components/examples/button/link"
import { ButtonLoading } from "@/components/examples/button/loading"
import { ButtonOutline } from "@/components/examples/button/outline"
import { ButtonSubtle } from "@/components/examples/button/subtle"
import { ButtonWithIcon } from "@/components/examples/button/with-icon"
import { CheckboxDemo } from "@/components/examples/checkbox/demo"
import { CheckboxDisabled } from "@/components/examples/checkbox/disabled"
import { CheckboxWithText } from "@/components/examples/checkbox/with-text"
import { CollapsibleDemo } from "@/components/examples/collapsible/demo"
import { ContextMenuDemo } from "@/components/examples/context-menu/demo"
import { DialogDemo } from "@/components/examples/dialog/demo"
import { DropdownMenuCheckboxes } from "@/components/examples/dropdown-menu/checkboxes"
import { DropdownMenuDemo } from "@/components/examples/dropdown-menu/demo"
import { DropdownMenuRadioGroupDemo } from "@/components/examples/dropdown-menu/radio-group"
import { HoverCardDemo } from "@/components/examples/hover-card/demo"
import { InputDemo } from "@/components/examples/input/demo"
import { InputDisabled } from "@/components/examples/input/disabled"
import { InputWithButton } from "@/components/examples/input/with-button"
import { InputWithLabel } from "@/components/examples/input/with-label"
import { InputWithText } from "@/components/examples/input/with-text"
import { LabelDemo } from "@/components/examples/label/demo"
import { MenubarDemo } from "@/components/examples/menubar/demo"
import { PopoverDemo } from "@/components/examples/popover/demo"
import { ProgressDemo } from "@/components/examples/progress/demo"
import { RadioGroupDemo } from "@/components/examples/radio-group/demo"
import { ScrollAreaDemo } from "@/components/examples/scroll-area/demo"
import { SelectDemo } from "@/components/examples/select/demo"
import { SeparatorDemo } from "@/components/examples/separator/demo"
import { SliderDemo } from "@/components/examples/slider/demo"
import { SwitchDemo } from "@/components/examples/switch/demo"
import { TabsDemo } from "@/components/examples/tabs/demo"
import { TextareaDemo } from "@/components/examples/textarea/demo"
import { TextareaDisabled } from "@/components/examples/textarea/disabled"
import { TextareaWithButton } from "@/components/examples/textarea/with-button"
import { TextareaWithLabel } from "@/components/examples/textarea/with-label"
import { TextareaWithText } from "@/components/examples/textarea/with-text"
import { TooltipDemo } from "@/components/examples/tooltip/demo"
import { TypographyBlockquote } from "@/components/examples/typography/blockquote"
import { TypographyDemo } from "@/components/examples/typography/demo"
import { TypographyH1 } from "@/components/examples/typography/h1"
import { TypographyH2 } from "@/components/examples/typography/h2"
import { TypographyH3 } from "@/components/examples/typography/h3"
import { TypographyH4 } from "@/components/examples/typography/h4"
import { TypographyInlineCode } from "@/components/examples/typography/inline-code"
import { TypographyLarge } from "@/components/examples/typography/large"
import { TypographyLead } from "@/components/examples/typography/lead"
import { TypographyList } from "@/components/examples/typography/list"
import { TypographyP } from "@/components/examples/typography/p"
import { TypographySmall } from "@/components/examples/typography/small"
import { TypographySubtle } from "@/components/examples/typography/subtle"
import { TypographyTable } from "@/components/examples/typography/table"
export const examples = {
AccordionDemo,
AlertDialogDemo,
AspectRatioDemo,
AvatarDemo,
ButtonDemo,
ButtonGhost,
ButtonLink,
ButtonLoading,
ButtonOutline,
ButtonSubtle,
ButtonWithIcon,
CheckboxDemo,
CheckboxDisabled,
CheckboxWithText,
CollapsibleDemo,
ContextMenuDemo,
DialogDemo,
DropdownMenuCheckboxes,
DropdownMenuDemo,
DropdownMenuRadioGroupDemo,
HoverCardDemo,
InputDemo,
InputDisabled,
InputWithButton,
InputWithLabel,
InputWithText,
LabelDemo,
MenubarDemo,
PopoverDemo,
ProgressDemo,
RadioGroupDemo,
ScrollAreaDemo,
SelectDemo,
SeparatorDemo,
SliderDemo,
SwitchDemo,
TabsDemo,
TextareaDemo,
TextareaDisabled,
TextareaWithButton,
TextareaWithLabel,
TextareaWithText,
TooltipDemo,
TypographyBlockquote,
TypographyDemo,
TypographyH1,
TypographyH2,
TypographyH3,
TypographyH4,
TypographyInlineCode,
TypographyLarge,
TypographyLead,
TypographyList,
TypographyP,
TypographySmall,
TypographySubtle,
TypographyTable,
}

View File

@@ -0,0 +1,5 @@
import { Input } from "@/components/ui/input"
export function InputDemo() {
return <Input type="email" placeholder="Email" />
}

View File

@@ -0,0 +1,5 @@
import { Input } from "@/components/ui/input"
export function InputDisabled() {
return <Input disabled type="email" placeholder="Email" />
}

View File

@@ -0,0 +1,11 @@
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function InputWithButton() {
return (
<div className="flex w-full max-w-sm items-center space-x-2">
<Input type="email" placeholder="Email" />
<Button type="submit">Subscribe</Button>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function InputWithLabel() {
return (
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="email">Email</Label>
<Input type="email" id="email" placeholder="Email" />
</div>
)
}

View File

@@ -0,0 +1,12 @@
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function InputWithText() {
return (
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="email-2">Email</Label>
<Input type="email" id="email-2" placeholder="Email" />
<p className="text-sm text-slate-500">Enter your email address.</p>
</div>
)
}

View File

@@ -0,0 +1,13 @@
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
export function LabelDemo() {
return (
<div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
</div>
)
}

View File

@@ -0,0 +1,109 @@
"use client"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
export function MenubarDemo() {
return (
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Search the web</MenubarItem>
<MenubarSeparator />
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem checked>
Always Show Full URLs
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>R</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value="benoit">
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Edit...</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}

View File

@@ -0,0 +1,69 @@
"use client"
import { Settings2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export function PopoverDemo() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-10 rounded-full p-0">
<Settings2 className="h-4 w-4" />
<span className="sr-only">Open popover</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="space-y-2">
<h4 className="font-medium leading-none">Dimensions</h4>
<p className="text-sm text-slate-500 dark:text-slate-400">
Set the dimensions for the layer.
</p>
</div>
<div className="grid gap-2">
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input
id="width"
defaultValue="100%"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input
id="maxWidth"
defaultValue="300px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input
id="height"
defaultValue="25px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxHeight">Max. height</Label>
<Input
id="maxHeight"
defaultValue="none"
className="col-span-2 h-8"
/>
</div>
</div>
</div>
</PopoverContent>
</Popover>
)
}

View File

@@ -0,0 +1,16 @@
"use client"
import * as React from "react"
import { Progress } from "@/components/ui/progress"
export function ProgressDemo() {
const [progress, setProgress] = React.useState(13)
React.useEffect(() => {
const timer = setTimeout(() => setProgress(66), 500)
return () => clearTimeout(timer)
}, [])
return <Progress value={progress} className="w-[60%]" />
}

View File

@@ -0,0 +1,23 @@
"use client"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export function RadioGroupDemo() {
return (
<RadioGroup defaultValue="comfortable">
<div className="flex items-center space-x-2">
<RadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">Default</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">Comfortable</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="compact" id="r3" />
<Label htmlFor="r3">Compact</Label>
</div>
</RadioGroup>
)
}

View File

@@ -0,0 +1,26 @@
import * as React from "react"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`
)
export function ScrollAreaDemo() {
return (
<ScrollArea className="h-72 w-48 rounded-md border border-slate-100 dark:border-slate-700">
<div className="p-4">
<h4 className="mb-4 text-sm font-medium leading-none">Tags</h4>
{tags.map((tag) => (
<React.Fragment>
<div className="text-sm" key={tag}>
{tag}
</div>
<Separator className="my-2" />
</React.Fragment>
))}
</div>
</ScrollArea>
)
}

View File

@@ -0,0 +1,53 @@
"use client"
import * as React from "react"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function SelectDemo() {
return (
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes">Grapes</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
<SelectItem value="aubergine">Aubergine</SelectItem>
<SelectItem value="broccoli">Broccoli</SelectItem>
<SelectItem value="carrot" disabled>
Carrot
</SelectItem>
<SelectItem value="courgette">Courgette</SelectItem>
<SelectItem value="leek">Leek</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Meat</SelectLabel>
<SelectItem value="beef">Beef</SelectItem>
<SelectItem value="chicken">Chicken</SelectItem>
<SelectItem value="lamb">Lamb</SelectItem>
<SelectItem value="pork">Pork</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
)
}

View File

@@ -0,0 +1,22 @@
import { Separator } from "@/components/ui/separator"
export function SeparatorDemo() {
return (
<div>
<div className="space-y-1">
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
<p className="text-sm text-slate-500 dark:text-slate-400">
An open-source UI component library.
</p>
</div>
<Separator className="my-4" />
<div className="flex h-5 items-center space-x-4 text-sm">
<div>Blog</div>
<Separator orientation="vertical" />
<div>Docs</div>
<Separator orientation="vertical" />
<div>Source</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,5 @@
import { Slider } from "@/components/ui/slider"
export function SliderDemo() {
return <Slider defaultValue={[50]} max={100} step={1} className="w-[60%]" />
}

View File

@@ -0,0 +1,11 @@
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
export function SwitchDemo() {
return (
<div className="flex items-center space-x-2">
<Switch id="airplane-mode" />
<Label htmlFor="airplane-mode">Airplane Mode</Label>
</div>
)
}

View File

@@ -0,0 +1,51 @@
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
export function TabsDemo() {
return (
<Tabs defaultValue="account" className="w-[400px]">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
<p className="text-sm text-slate-500 dark:text-slate-400">
Make changes to your account here. Click save when you're done.
</p>
<div className="grid gap-2 py-4">
<div className="space-y-1">
<Label htmlFor="name">Name</Label>
<Input id="name" defaultValue="Pedro Duarte" />
</div>
<div className="space-y-1">
<Label htmlFor="username">Username</Label>
<Input id="username" defaultValue="@peduarte" />
</div>
</div>
<div className="flex">
<Button>Save changes</Button>
</div>
</TabsContent>
<TabsContent value="password">
<p className="text-sm text-slate-500 dark:text-slate-400">
Change your password here. After saving, you'll be logged out.
</p>
<div className="grid gap-2 py-4">
<div className="space-y-1">
<Label htmlFor="current">Current password</Label>
<Input id="current" type="password" />
</div>
<div className="space-y-1">
<Label htmlFor="new">New password</Label>
<Input id="new" type="password" />
</div>
</div>
<div className="flex">
<Button>Save password</Button>
</div>
</TabsContent>
</Tabs>
)
}

View File

@@ -0,0 +1,5 @@
import { Textarea } from "@/components/ui/textarea"
export function TextareaDemo() {
return <Textarea placeholder="Type your message here." />
}

View File

@@ -0,0 +1,5 @@
import { Textarea } from "@/components/ui/textarea"
export function TextareaDisabled() {
return <Textarea placeholder="Type your message here." disabled />
}

View File

@@ -0,0 +1,11 @@
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
export function TextareaWithButton() {
return (
<div className="grid w-full gap-2">
<Textarea placeholder="Type your message here." />
<Button>Send message</Button>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
export function TextareaWithLabel() {
return (
<div className="grid w-full gap-1.5">
<Label htmlFor="message">Your message</Label>
<Textarea placeholder="Type your message here." id="message" />
</div>
)
}

View File

@@ -0,0 +1,14 @@
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
export function TextareaWithText() {
return (
<div className="grid w-full gap-1.5">
<Label htmlFor="message-2">Your Message</Label>
<Textarea placeholder="Type your message here." id="message-2" />
<p className="text-sm text-slate-500">
Your message will be copied to the support team.
</p>
</div>
)
}

View File

@@ -0,0 +1,26 @@
"use client"
import { Plus } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
export function TooltipDemo() {
return (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" className="w-10 rounded-full p-0">
<Plus className="h-4 w-4" />
<span className="sr-only">Add</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Add to library</p>
</TooltipContent>
</Tooltip>
)
}

View File

@@ -0,0 +1,8 @@
export function TypographyBlockquote() {
return (
<blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200">
"After all," he said, "everyone enjoys a good joke, so it's only fair that
they should pay for the privilege."
</blockquote>
)
}

View File

@@ -0,0 +1,119 @@
export function TypographyDemo() {
return (
<div>
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
The Joke Tax Chronicles
</h1>
<p className="leading-7 [&:not(:first-child)]:mt-6">
Once upon a time, in a far-off land, there was a very lazy king who
spent all day lounging on his throne. One day, his advisors came to him
with a problem: the kingdom was running out of money.
</p>
<h2 className="mt-10 scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700">
The King's Plan
</h2>
<p className="leading-7 [&:not(:first-child)]:mt-6">
The king thought long and hard, and finally came up with{" "}
<a
href="#"
className="font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50"
>
a brilliant plan
</a>
: he would tax the jokes in the kingdom.
</p>
<blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200">
"After all," he said, "everyone enjoys a good joke, so it's only fair
that they should pay for the privilege."
</blockquote>
<h3 className="mt-8 scroll-m-20 text-2xl font-semibold tracking-tight">
The Joke Tax
</h3>
<p className="leading-7 [&:not(:first-child)]:mt-6">
The king's subjects were not amused. They grumbled and complained, but
the king was firm:
</p>
<ul className="my-6 ml-6 list-disc [&>li]:mt-2">
<li>1st level of puns: 5 gold coins</li>
<li>2nd level of jokes: 10 gold coins</li>
<li>3rd level of one-liners : 20 gold coins</li>
</ul>
<p className="leading-7 [&:not(:first-child)]:mt-6">
As a result, people stopped telling jokes, and the kingdom fell into a
gloom. But there was one person who refused to let the king's
foolishness get him down: a court jester named Jokester.
</p>
<h3 className="mt-8 scroll-m-20 text-2xl font-semibold tracking-tight">
Jokester's Revolt
</h3>
<p className="leading-7 [&:not(:first-child)]:mt-6">
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.
</p>
<p className="leading-7 [&:not(:first-child)]:mt-6">
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.
</p>
<h3 className="mt-8 scroll-m-20 text-2xl font-semibold tracking-tight">
The People's Rebellion
</h3>
<p className="leading-7 [&:not(:first-child)]:mt-6">
The people of the kingdom, feeling uplifted by the laughter, started to
tell jokes and puns again, and soon the entire kingdom was in on the
joke.
</p>
<div className="my-6 w-full overflow-y-auto">
<table className="w-full">
<thead>
<tr className="m-0 border-t border-slate-300 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<th className="border border-slate-200 px-4 py-2 text-left font-bold dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
King's Treasury
</th>
<th className="border border-slate-200 px-4 py-2 text-left font-bold dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
People's happiness
</th>
</tr>
</thead>
<tbody>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Empty
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Overflowing
</td>
</tr>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Modest
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Satisfied
</td>
</tr>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-600 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Full
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Ecstatic
</td>
</tr>
</tbody>
</table>
</div>
<p className="leading-7 [&:not(:first-child)]:mt-6">
The king, seeing how much happier his subjects were, realized the error
of his ways and repealed the joke tax. Jokester was declared a hero, and
the kingdom lived happily ever after.
</p>
<p className="leading-7 [&:not(:first-child)]:mt-6">
The moral of the story is: never underestimate the power of a good laugh
and always be careful of bad ideas.
</p>
</div>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyH1() {
return (
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
Taxing Laughter: The Joke Tax Chronicles
</h1>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyH2() {
return (
<h2 className="mt-10 scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700">
The People of the Kingdom
</h2>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyH3() {
return (
<h3 className="mt-8 scroll-m-20 text-2xl font-semibold tracking-tight">
The Joke Tax
</h3>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyH4() {
return (
<h4 className="mt-8 scroll-m-20 text-xl font-semibold tracking-tight">
People stopped telling jokes
</h4>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyInlineCode() {
return (
<code className="relative rounded bg-slate-100 py-[0.2rem] px-[0.3rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400">
@radix-ui/react-alert-dialog
</code>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographyLarge() {
return (
<div className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Are you sure absolutely sure?
</div>
)
}

View File

@@ -0,0 +1,8 @@
export function TypographyLead() {
return (
<p className="text-xl text-slate-700 dark:text-slate-400">
A modal dialog that interrupts the user with important content and expects
a response.
</p>
)
}

View File

@@ -0,0 +1,9 @@
export function TypographyList() {
return (
<ul className="my-6 ml-6 list-disc [&>li]:mt-2">
<li>1st level of puns: 5 gold coins</li>
<li>2nd level of jokes: 10 gold coins</li>
<li>3rd level of one-liners : 20 gold coins</li>
</ul>
)
}

View File

@@ -0,0 +1,8 @@
export function TypographyP() {
return (
<p className="leading-7 [&:not(:first-child)]:mt-6">
The king, seeing how much happier his subjects were, realized the error of
his ways and repealed the joke tax.
</p>
)
}

View File

@@ -0,0 +1,5 @@
export function TypographySmall() {
return (
<small className="text-sm font-medium leading-none">Email address</small>
)
}

View File

@@ -0,0 +1,7 @@
export function TypographySubtle() {
return (
<p className="text-sm text-slate-500 dark:text-slate-400">
Enter your email address.
</p>
)
}

View File

@@ -0,0 +1,44 @@
export function TypographyTable() {
return (
<div className="my-6 w-full overflow-y-auto">
<table className="w-full">
<thead>
<tr className="m-0 border-t border-slate-300 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<th className="border border-slate-200 px-4 py-2 text-left font-bold dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
King's Treasury
</th>
<th className="border border-slate-200 px-4 py-2 text-left font-bold dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
People's happiness
</th>
</tr>
</thead>
<tbody>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Empty
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Overflowing
</td>
</tr>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-700 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Modest
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Satisfied
</td>
</tr>
<tr className="m-0 border-t border-slate-200 p-0 even:bg-slate-100 dark:border-slate-600 dark:even:bg-slate-800">
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Full
</td>
<td className="border border-slate-200 px-4 py-2 text-left dark:border-slate-700 [&[align=center]]:text-center [&[align=right]]:text-right">
Ecstatic
</td>
</tr>
</tbody>
</table>
</div>
)
}

View File

@@ -0,0 +1,29 @@
"use client"
import {
JetBrains_Mono as FontMono,
Inter as FontSans,
} from "@next/font/google"
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
})
const fontMono = FontMono({
subsets: ["latin"],
variable: "--font-mono",
})
export function Fonts() {
return (
<>
<style jsx global>{`
:root {
--font-sans: ${fontSans.style.fontFamily};
--font-mono: ${fontMono.style.fontFamily};
}
}`}</style>
</>
)
}

View File

@@ -0,0 +1,117 @@
import {
AlertTriangle,
ArrowRight,
Check,
ChevronLeft,
ChevronRight,
Circle,
ClipboardCheck,
Copy,
CreditCard,
File,
FileText,
HelpCircle,
Image,
Laptop,
Loader2,
LucideProps,
Moon,
MoreVertical,
Pizza,
Plus,
Settings,
SunMedium,
Trash,
Twitter,
User,
X,
type Icon as LucideIcon,
} from "lucide-react"
export type Icon = LucideIcon
export const Icons = {
logo: (props: LucideProps) => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<circle cx="12" cy="12" r="10"></circle>
</svg>
),
close: X,
spinner: Loader2,
chevronLeft: ChevronLeft,
chevronRight: ChevronRight,
trash: Trash,
post: FileText,
page: File,
media: Image,
settings: Settings,
billing: CreditCard,
ellipsis: MoreVertical,
add: Plus,
warning: AlertTriangle,
user: User,
arrowRight: ArrowRight,
help: HelpCircle,
pizza: Pizza,
twitter: Twitter,
check: Check,
copy: Copy,
copyDone: ClipboardCheck,
sun: SunMedium,
moon: Moon,
laptop: Laptop,
gitHub: (props: LucideProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
),
radix: (props: LucideProps) => (
<svg viewBox="0 0 25 25" fill="none" {...props}>
<path
d="M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z"
fill="currentcolor"
></path>
<path d="M12 0H4V8H12V0Z" fill="currentcolor"></path>
<path
d="M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z"
fill="currentcolor"
></path>
</svg>
),
npm: (props: LucideProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" />
</svg>
),
yarn: (props: LucideProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z" />
</svg>
),
pnpm: (props: LucideProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z" />
</svg>
),
react: (props: LucideProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
</svg>
),
tailwind: (props: LucideProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z" />
</svg>
),
}

View File

@@ -0,0 +1,112 @@
"use client"
import * as React from "react"
import Link from "next/link"
import { useSelectedLayoutSegment } from "next/navigation"
import { MainNavItem } from "types/nav"
import { docsConfig } from "@/config/docs"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ScrollArea } from "@/components/ui/scroll-area"
interface MainNavProps {
items?: MainNavItem[]
children?: React.ReactNode
}
export function MainNav({ items, children }: MainNavProps) {
const segment = useSelectedLayoutSegment()
return (
<div className="flex gap-6 md:gap-10">
<Link href="/" className="hidden items-center space-x-2 md:flex">
<Icons.logo className="h-6 w-6" />
<span className="hidden font-bold sm:inline-block">
{siteConfig.name}
</span>
</Link>
{items?.length ? (
<nav className="hidden gap-6 md:flex">
{items?.map(
(item, index) =>
item.href && (
<Link
key={index}
href={item.href}
className={cn(
"flex items-center text-lg font-semibold text-slate-600 hover:text-slate-900 dark:text-slate-100 sm:text-sm",
item.href.startsWith(`/${segment}`) && "text-slate-900",
item.disabled && "cursor-not-allowed opacity-80"
)}
>
{item.title}
</Link>
)
)}
</nav>
) : null}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="-ml-4 text-base hover:bg-transparent focus:ring-0 md:hidden"
>
<Icons.logo className="mr-2 h-4 w-4" />{" "}
<span className="font-bold">Menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
sideOffset={24}
className="w-[300px] overflow-scroll"
>
<DropdownMenuLabel>
<Link href="/" className="flex items-center">
<Icons.logo className="mr-2 h-4 w-4" /> {siteConfig.name}
</Link>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<ScrollArea className="h-[400px]">
{items?.map(
(item, index) =>
item.href && (
<DropdownMenuItem key={index} asChild>
<Link href={item.href}>{item.title}</Link>
</DropdownMenuItem>
)
)}
{docsConfig.sidebarNav.map((item, index) => (
<DropdownMenuGroup key={index}>
<DropdownMenuSeparator />
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
<DropdownMenuSeparator />
{item?.items?.length &&
item.items.map((item) => (
<DropdownMenuItem key={item.title} asChild>
{item.href ? (
<Link href={item.href}>{item.title}</Link>
) : (
item.title
)}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
))}
</ScrollArea>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}

View File

@@ -0,0 +1,67 @@
import { allDocuments } from "contentlayer/generated"
import * as z from "zod"
import { siteConfig } from "@/config/site"
import { absoluteUrl } from "@/lib/utils"
import { ogImageSchema } from "@/lib/validations/og"
interface MdxHeadProps {
params: {
slug?: string[]
}
og?: z.infer<typeof ogImageSchema>
}
export default function MdxHead({ params, og }: MdxHeadProps) {
const slug = params?.slug?.join("/") || ""
const mdxDoc = allDocuments.find((doc) => doc.slugAsParams === slug)
if (!mdxDoc) {
return null
}
const title = `${mdxDoc.title} - ${siteConfig.name}`
const url = process.env.NEXT_PUBLIC_APP_URL
const ogUrl = new URL(`${url}/og.jpg`)
const ogTitle = og?.heading || mdxDoc.title
const ogDescription = mdxDoc.description || siteConfig.description
return (
<>
<title>{title}</title>
<link rel="canonical" href={absoluteUrl(mdxDoc.slug)} />
<meta name="description" content={ogDescription} />
<meta charSet="utf-8" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} />
<meta property="og:description" content={ogDescription} />
<meta property="og:url" content={url} />
<meta property="og:image" content={ogUrl.toString()} />
<meta name="twitter:title" content={ogTitle} />
<meta name="twitter:description" content={ogDescription} />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url} />
<meta name="twitter:image" content={ogUrl.toString()} />
</>
)
}

247
apps/www/components/mdx.tsx Normal file
View File

@@ -0,0 +1,247 @@
import * as React from "react"
import Image from "next/image"
import { useMDXComponent } from "next-contentlayer/hooks"
import { NpmCommands } from "types/unist"
import { cn } from "@/lib/utils"
import { Callout } from "@/components/callout"
import { Card } from "@/components/card"
import { CodeBlockWrapper } from "@/components/code-block-wrapper"
import { ComponentExample } from "@/components/component-example"
import { ComponentSource } from "@/components/component-source"
import { CopyButton, CopyNpmCommandButton } from "@/components/copy-button"
import { examples } from "@/components/examples"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
const components = {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
h1: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1
className={cn(
"mt-2 scroll-m-20 text-4xl font-bold tracking-tight",
className
)}
{...props}
/>
),
h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h2
className={cn(
"mt-12 scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight first:mt-0 dark:border-b-slate-700",
className
)}
{...props}
/>
),
h3: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h3
className={cn(
"mt-8 scroll-m-20 text-2xl font-semibold tracking-tight",
className
)}
{...props}
/>
),
h4: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h4
className={cn(
"mt-8 scroll-m-20 text-xl font-semibold tracking-tight",
className
)}
{...props}
/>
),
h5: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h5
className={cn(
"mt-8 scroll-m-20 text-lg font-semibold tracking-tight",
className
)}
{...props}
/>
),
h6: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h6
className={cn(
"mt-8 scroll-m-20 text-base font-semibold tracking-tight",
className
)}
{...props}
/>
),
a: ({ className, ...props }: React.HTMLAttributes<HTMLAnchorElement>) => (
<a
className={cn(
"font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50",
className
)}
{...props}
/>
),
p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (
<p
className={cn("leading-7 [&:not(:first-child)]:mt-6", 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 border-slate-300 pl-6 italic text-slate-800 [&>*]:text-slate-600",
className
)}
{...props}
/>
),
img: ({
className,
alt,
...props
}: React.ImgHTMLAttributes<HTMLImageElement>) => (
// eslint-disable-next-line @next/next/no-img-element
<img
className={cn("rounded-md border border-slate-200", className)}
alt={alt}
{...props}
/>
),
hr: ({ ...props }: React.HTMLAttributes<HTMLHRElement>) => (
<hr
className="my-4 border-slate-200 dark:border-slate-700 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 border-slate-300 p-0 even:bg-slate-100",
className
)}
{...props}
/>
),
th: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<th
className={cn(
"border border-slate-200 px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
td: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<td
className={cn(
"border border-slate-200 px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
/>
),
pre: ({
className,
__rawString__,
__npmCommand__,
__pnpmCommand__,
__yarnCommand__,
__withMeta__,
__src__,
...props
}: React.HTMLAttributes<HTMLPreElement> & {
__rawString__?: string
__withMeta__?: boolean
__src__?: string
} & NpmCommands) => {
return (
<>
<pre
className={cn(
"mt-6 mb-4 overflow-x-auto rounded-lg border border-slate-900 bg-slate-900 py-4 px-2 dark:border-slate-700 dark:bg-black",
className
)}
{...props}
/>
{__rawString__ && !__npmCommand__ && (
<CopyButton
value={__rawString__}
src={__src__}
className={cn(
"absolute top-4 right-4 border-none text-slate-300 opacity-50 hover:bg-transparent hover:opacity-100",
__withMeta__ && "top-20"
)}
/>
)}
{__npmCommand__ && __yarnCommand__ && __pnpmCommand__ && (
<CopyNpmCommandButton
commands={{
__npmCommand__,
__pnpmCommand__,
__yarnCommand__,
}}
className={cn(
"absolute top-4 right-4 border-none text-slate-300 opacity-50 hover:bg-transparent hover:opacity-100",
__withMeta__ && "top-20"
)}
/>
)}
</>
)
},
code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (
<code
className={cn(
"relative rounded bg-slate-100 py-[0.2rem] px-[0.3rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400",
className
)}
{...props}
/>
),
Image,
Callout,
Card,
ComponentExample,
ComponentSource,
CodeBlockWrapper: ({ ...props }) => (
<CodeBlockWrapper
className="rounded-md border border-slate-100"
{...props}
/>
),
...examples,
}
interface MdxProps {
code: string
}
export function Mdx({ code }: MdxProps) {
const Component = useMDXComponent(code)
return (
<div className="mdx">
<Component components={components} />
</div>
)
}

View File

@@ -0,0 +1,42 @@
"use client"
import * as React from "react"
import { useTheme } from "next-themes"
import { Icons } from "@/components/icons"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ModeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<Icons.sun className="hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<Icons.sun className="mr-2 h-4 w-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<Icons.moon className="mr-2 h-4 w-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<Icons.laptop className="mr-2 h-4 w-4" />
<span>System</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -0,0 +1,32 @@
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
interface DocsPageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
heading: string
text?: string
}
export function DocsPageHeader({
heading,
text,
className,
children,
...props
}: DocsPageHeaderProps) {
return (
<>
<div className={cn("space-y-4", className)} {...props}>
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
{heading}
</h1>
{text && (
<p className="max-w-[95%] text-xl text-slate-700 dark:text-slate-400">
{text}
</p>
)}
</div>
{children}
<Separator className="my-4 md:my-6" />
</>
)
}

View File

@@ -0,0 +1,64 @@
import Link from "next/link"
import { Doc } from "contentlayer/generated"
import { NavItem, NavItemWithChildren } from "types/nav"
import { docsConfig } from "@/config/docs"
import { Icons } from "@/components/icons"
import { buttonVariants } from "@/components/ui/button"
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={buttonVariants({ variant: "outline" })}
>
<Icons.chevronLeft className="mr-2 h-4 w-4" />
{pager.prev.title}
</Link>
)}
{pager?.next?.href && (
<Link
href={pager.next.href}
className={buttonVariants({ variant: "outline" })}
>
{pager.next.title}
<Icons.chevronRight className="ml-2 h-4 w-4" />
</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)
}, [])
}

View File

@@ -0,0 +1,19 @@
"use client"
import { AspectRatio } from "@/components/ui/aspect-ratio"
export function PromoVideo() {
return (
<AspectRatio
ratio={16 / 9}
className="overflow-hidden rounded-lg border bg-white shadow-xl"
>
<video autoPlay muted playsInline>
<source
src="https://ui-shadcn.s3.amazonaws.com/ui-promo-hd.mp4"
type="video/mp4"
/>
</video>
</AspectRatio>
)
}

View File

@@ -0,0 +1,32 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> {}
export function DocsSearch({ className, ...props }: DocsSearchProps) {
function onSubmit(event: React.SyntheticEvent) {
event.preventDefault()
}
return (
<form
onSubmit={onSubmit}
className={cn("relative w-full", className)}
{...props}
>
<Input
type="search"
placeholder="Search documentation..."
className="h-9 sm:w-64 sm:pr-12"
disabled
/>
<kbd className="pointer-events-none absolute top-2 right-1.5 hidden h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400 sm:flex">
<span className="text-xs"></span>K
</kbd>
</form>
)
}

View File

@@ -0,0 +1,71 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { SidebarNavItem } from "types/nav"
import { cn } from "@/lib/utils"
export interface DocsSidebarNavProps {
items: SidebarNavItem[]
}
export function DocsSidebarNav({ items }: DocsSidebarNavProps) {
const pathname = usePathname()
return items.length ? (
<div className="w-full">
{items.map((item, index) => (
<div key={index} className={cn("pb-8")}>
<h4 className="mb-1 rounded-md px-2 py-1 text-sm font-semibold">
{item.title}
</h4>
{item?.items?.length && (
<DocsSidebarNavItems items={item.items} pathname={pathname} />
)}
</div>
))}
</div>
) : null
}
interface DocsSidebarNavItemsProps {
items: SidebarNavItem[]
pathname: string | null
}
export function DocsSidebarNavItems({
items,
pathname,
}: DocsSidebarNavItemsProps) {
return items?.length ? (
<div className="grid grid-flow-row auto-rows-max text-sm">
{items.map((item, index) =>
item.href ? (
<Link
key={index}
href={item.href}
className={cn(
"flex w-full items-center rounded-md p-2 hover:underline",
item.disabled && "cursor-not-allowed opacity-60",
{
"bg-slate-100 dark:bg-slate-800": pathname === item.href,
}
)}
target={item.external ? "_blank" : ""}
rel={item.external ? "noreferrer" : ""}
>
{item.title}
</Link>
) : (
<span
key={index}
className="flex w-full cursor-not-allowed items-center rounded-md p-2 opacity-60 hover:underline"
>
{item.title}
</span>
)
)}
</div>
) : null
}

View File

@@ -0,0 +1,35 @@
import { siteConfig } from "@/config/site"
import { Icons } from "@/components/icons"
export function SiteFooter() {
return (
<footer className="container">
<div className="flex flex-col items-center justify-between gap-4 border-t border-t-slate-200 py-10 dark:border-t-slate-700 md:h-24 md:flex-row md:py-0">
<div className="flex flex-col items-center gap-4 px-8 md:flex-row md:gap-2 md:px-0">
<Icons.logo className="h-6 w-6" />
<p className="text-center text-sm leading-loose text-slate-600 dark:text-slate-400 md:text-left">
Built by{" "}
<a
href={siteConfig.links.twitter}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
shadcn
</a>
. The source code is available on{" "}
<a
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
GitHub
</a>
.
</p>
</div>
</div>
</footer>
)
}

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