mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00: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