mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-02-08 18:39:31 +08:00
feat: initial commit
This commit is contained in:
3
.commitlintrc.json
Normal file
3
.commitlintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
10
.editorconfig
Normal file
10
.editorconfig
Normal 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
24
.eslintrc.json
Normal 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
35
.gitignore
vendored
Normal 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
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx commitlint --edit $1
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "packages/*/" }
|
||||
]
|
||||
}
|
||||
46
README.md
Normal file
46
README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# shadcn/ui
|
||||
|
||||
Beautifully designed components built with Radix UI and Tailwind CSS.
|
||||
|
||||

|
||||
|
||||
## 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
4
apps/www/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# App
|
||||
# -----------------------------------------------------------------------------
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
2
apps/www/.gitignore
vendored
Normal file
2
apps/www/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
.env
|
||||
25
apps/www/app/docs/[[...slug]]/head.tsx
Normal file
25
apps/www/app/docs/[[...slug]]/head.tsx
Normal 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" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
80
apps/www/app/docs/[[...slug]]/page.tsx
Normal file
80
apps/www/app/docs/[[...slug]]/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
20
apps/www/app/docs/layout.tsx
Normal file
20
apps/www/app/docs/layout.tsx
Normal 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
43
apps/www/app/head.tsx
Normal 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
44
apps/www/app/layout.tsx
Normal 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
71
apps/www/app/page.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
BIN
apps/www/assets/fonts/Inter-Bold.ttf
Normal file
BIN
apps/www/assets/fonts/Inter-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/www/assets/fonts/Inter-Regular.ttf
Normal file
BIN
apps/www/assets/fonts/Inter-Regular.ttf
Normal file
Binary file not shown.
7
apps/www/components/analytics.tsx
Normal file
7
apps/www/components/analytics.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
|
||||
|
||||
export function Analytics() {
|
||||
return <VercelAnalytics />
|
||||
}
|
||||
745
apps/www/components/apple-music-demo.tsx
Normal file
745
apps/www/components/apple-music-demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
31
apps/www/components/callout.tsx
Normal file
31
apps/www/components/callout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
38
apps/www/components/card.tsx
Normal file
38
apps/www/components/card.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
56
apps/www/components/code-block-wrapper.tsx
Normal file
56
apps/www/components/code-block-wrapper.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
21
apps/www/components/component-card.tsx
Normal file
21
apps/www/components/component-card.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
77
apps/www/components/component-example.tsx
Normal file
77
apps/www/components/component-example.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
21
apps/www/components/component-source.tsx
Normal file
21
apps/www/components/component-source.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
190
apps/www/components/copy-button.tsx
Normal file
190
apps/www/components/copy-button.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
6
apps/www/components/examples/.eslintrc.json
Normal file
6
apps/www/components/examples/.eslintrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/eslintrc",
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
||||
32
apps/www/components/examples/accordion/demo.tsx
Normal file
32
apps/www/components/examples/accordion/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
37
apps/www/components/examples/alert-dialog/demo.tsx
Normal file
37
apps/www/components/examples/alert-dialog/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
18
apps/www/components/examples/aspect-ratio/demo.tsx
Normal file
18
apps/www/components/examples/aspect-ratio/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
12
apps/www/components/examples/avatar/demo.tsx
Normal file
12
apps/www/components/examples/avatar/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
apps/www/components/examples/button/demo.tsx
Normal file
5
apps/www/components/examples/button/demo.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ButtonDemo() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
5
apps/www/components/examples/button/ghost.tsx
Normal file
5
apps/www/components/examples/button/ghost.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
5
apps/www/components/examples/button/link.tsx
Normal file
5
apps/www/components/examples/button/link.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
12
apps/www/components/examples/button/loading.tsx
Normal file
12
apps/www/components/examples/button/loading.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
apps/www/components/examples/button/outline.tsx
Normal file
5
apps/www/components/examples/button/outline.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
5
apps/www/components/examples/button/subtle.tsx
Normal file
5
apps/www/components/examples/button/subtle.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function ButtonSubtle() {
|
||||
return <Button variant="subtle">Subtle</Button>
|
||||
}
|
||||
11
apps/www/components/examples/button/with-icon.tsx
Normal file
11
apps/www/components/examples/button/with-icon.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
17
apps/www/components/examples/checkbox/demo.tsx
Normal file
17
apps/www/components/examples/checkbox/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
17
apps/www/components/examples/checkbox/disabled.tsx
Normal file
17
apps/www/components/examples/checkbox/disabled.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
apps/www/components/examples/checkbox/with-text.tsx
Normal file
22
apps/www/components/examples/checkbox/with-text.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
46
apps/www/components/examples/collapsible/demo.tsx
Normal file
46
apps/www/components/examples/collapsible/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
89
apps/www/components/examples/context-menu/demo.tsx
Normal file
89
apps/www/components/examples/context-menu/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
49
apps/www/components/examples/dialog/demo.tsx
Normal file
49
apps/www/components/examples/dialog/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
53
apps/www/components/examples/dropdown-menu/checkboxes.tsx
Normal file
53
apps/www/components/examples/dropdown-menu/checkboxes.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
124
apps/www/components/examples/dropdown-menu/demo.tsx
Normal file
124
apps/www/components/examples/dropdown-menu/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
37
apps/www/components/examples/dropdown-menu/radio-group.tsx
Normal file
37
apps/www/components/examples/dropdown-menu/radio-group.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
39
apps/www/components/examples/hover-card/demo.tsx
Normal file
39
apps/www/components/examples/hover-card/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
117
apps/www/components/examples/index.tsx
Normal file
117
apps/www/components/examples/index.tsx
Normal 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,
|
||||
}
|
||||
5
apps/www/components/examples/input/demo.tsx
Normal file
5
apps/www/components/examples/input/demo.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function InputDemo() {
|
||||
return <Input type="email" placeholder="Email" />
|
||||
}
|
||||
5
apps/www/components/examples/input/disabled.tsx
Normal file
5
apps/www/components/examples/input/disabled.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function InputDisabled() {
|
||||
return <Input disabled type="email" placeholder="Email" />
|
||||
}
|
||||
11
apps/www/components/examples/input/with-button.tsx
Normal file
11
apps/www/components/examples/input/with-button.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
11
apps/www/components/examples/input/with-label.tsx
Normal file
11
apps/www/components/examples/input/with-label.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
12
apps/www/components/examples/input/with-text.tsx
Normal file
12
apps/www/components/examples/input/with-text.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
13
apps/www/components/examples/label/demo.tsx
Normal file
13
apps/www/components/examples/label/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
109
apps/www/components/examples/menubar/demo.tsx
Normal file
109
apps/www/components/examples/menubar/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
69
apps/www/components/examples/popover/demo.tsx
Normal file
69
apps/www/components/examples/popover/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
16
apps/www/components/examples/progress/demo.tsx
Normal file
16
apps/www/components/examples/progress/demo.tsx
Normal 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%]" />
|
||||
}
|
||||
23
apps/www/components/examples/radio-group/demo.tsx
Normal file
23
apps/www/components/examples/radio-group/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
26
apps/www/components/examples/scroll-area/demo.tsx
Normal file
26
apps/www/components/examples/scroll-area/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
53
apps/www/components/examples/select/demo.tsx
Normal file
53
apps/www/components/examples/select/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
apps/www/components/examples/separator/demo.tsx
Normal file
22
apps/www/components/examples/separator/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
apps/www/components/examples/slider/demo.tsx
Normal file
5
apps/www/components/examples/slider/demo.tsx
Normal 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%]" />
|
||||
}
|
||||
11
apps/www/components/examples/switch/demo.tsx
Normal file
11
apps/www/components/examples/switch/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
51
apps/www/components/examples/tabs/demo.tsx
Normal file
51
apps/www/components/examples/tabs/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
apps/www/components/examples/textarea/demo.tsx
Normal file
5
apps/www/components/examples/textarea/demo.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
export function TextareaDemo() {
|
||||
return <Textarea placeholder="Type your message here." />
|
||||
}
|
||||
5
apps/www/components/examples/textarea/disabled.tsx
Normal file
5
apps/www/components/examples/textarea/disabled.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
export function TextareaDisabled() {
|
||||
return <Textarea placeholder="Type your message here." disabled />
|
||||
}
|
||||
11
apps/www/components/examples/textarea/with-button.tsx
Normal file
11
apps/www/components/examples/textarea/with-button.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
11
apps/www/components/examples/textarea/with-label.tsx
Normal file
11
apps/www/components/examples/textarea/with-label.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
14
apps/www/components/examples/textarea/with-text.tsx
Normal file
14
apps/www/components/examples/textarea/with-text.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
26
apps/www/components/examples/tooltip/demo.tsx
Normal file
26
apps/www/components/examples/tooltip/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
apps/www/components/examples/typography/blockquote.tsx
Normal file
8
apps/www/components/examples/typography/blockquote.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
119
apps/www/components/examples/typography/demo.tsx
Normal file
119
apps/www/components/examples/typography/demo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/h1.tsx
Normal file
7
apps/www/components/examples/typography/h1.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/h2.tsx
Normal file
7
apps/www/components/examples/typography/h2.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/h3.tsx
Normal file
7
apps/www/components/examples/typography/h3.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/h4.tsx
Normal file
7
apps/www/components/examples/typography/h4.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/inline-code.tsx
Normal file
7
apps/www/components/examples/typography/inline-code.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/large.tsx
Normal file
7
apps/www/components/examples/typography/large.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
apps/www/components/examples/typography/lead.tsx
Normal file
8
apps/www/components/examples/typography/lead.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
9
apps/www/components/examples/typography/list.tsx
Normal file
9
apps/www/components/examples/typography/list.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
apps/www/components/examples/typography/p.tsx
Normal file
8
apps/www/components/examples/typography/p.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
apps/www/components/examples/typography/small.tsx
Normal file
5
apps/www/components/examples/typography/small.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export function TypographySmall() {
|
||||
return (
|
||||
<small className="text-sm font-medium leading-none">Email address</small>
|
||||
)
|
||||
}
|
||||
7
apps/www/components/examples/typography/subtle.tsx
Normal file
7
apps/www/components/examples/typography/subtle.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
44
apps/www/components/examples/typography/table.tsx
Normal file
44
apps/www/components/examples/typography/table.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
29
apps/www/components/fonts.tsx
Normal file
29
apps/www/components/fonts.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
117
apps/www/components/icons.tsx
Normal file
117
apps/www/components/icons.tsx
Normal 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>
|
||||
),
|
||||
}
|
||||
112
apps/www/components/main-nav.tsx
Normal file
112
apps/www/components/main-nav.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
67
apps/www/components/mdx-head.tsx
Normal file
67
apps/www/components/mdx-head.tsx
Normal 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
247
apps/www/components/mdx.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
42
apps/www/components/mode-toggle.tsx
Normal file
42
apps/www/components/mode-toggle.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
32
apps/www/components/page-header.tsx
Normal file
32
apps/www/components/page-header.tsx
Normal 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" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
64
apps/www/components/pager.tsx
Normal file
64
apps/www/components/pager.tsx
Normal 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)
|
||||
}, [])
|
||||
}
|
||||
19
apps/www/components/promo-video.tsx
Normal file
19
apps/www/components/promo-video.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
32
apps/www/components/search.tsx
Normal file
32
apps/www/components/search.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
71
apps/www/components/sidebar-nav.tsx
Normal file
71
apps/www/components/sidebar-nav.tsx
Normal 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
|
||||
}
|
||||
35
apps/www/components/site-footer.tsx
Normal file
35
apps/www/components/site-footer.tsx
Normal 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
Reference in New Issue
Block a user