mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-12 02:11:33 +00:00
Compare commits
111 Commits
@shadcn/ui
...
shadcn-ui@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0857bfe889 | ||
|
|
ea6699adbf | ||
|
|
1f004243d4 | ||
|
|
eee7ce6879 | ||
|
|
c3377530f4 | ||
|
|
a8bb2ef737 | ||
|
|
ab836d1ab3 | ||
|
|
77d6f5676e | ||
|
|
2846b2ea9a | ||
|
|
0f84973b4d | ||
|
|
f8348621f4 | ||
|
|
fbed50f4e8 | ||
|
|
1971fa7511 | ||
|
|
7b5582e5d0 | ||
|
|
2ca7476c9b | ||
|
|
aea12e9762 | ||
|
|
343acb3a51 | ||
|
|
cf139e5fa1 | ||
|
|
38fb9693d0 | ||
|
|
f1de3401a2 | ||
|
|
379d1560c3 | ||
|
|
d604b82fb3 | ||
|
|
658c710bce | ||
|
|
4ca9619efa | ||
|
|
eeb17545a1 | ||
|
|
3d717f992b | ||
|
|
042554ad07 | ||
|
|
71f496d41f | ||
|
|
0a5df3ac85 | ||
|
|
eb27529f50 | ||
|
|
dffbe89f7d | ||
|
|
22f23b7db3 | ||
|
|
d6d4017b95 | ||
|
|
00ecdfbb17 | ||
|
|
065ba02ae5 | ||
|
|
5dfc2020aa | ||
|
|
5aecccc586 | ||
|
|
97a444b210 | ||
|
|
060e896183 | ||
|
|
c4da22ffe9 | ||
|
|
f6b2d0c5dd | ||
|
|
3f01388389 | ||
|
|
c584f01163 | ||
|
|
588ebd79e4 | ||
|
|
36881682cf | ||
|
|
d1363515eb | ||
|
|
5afb8d530f | ||
|
|
6a5195498f | ||
|
|
7d8be94a01 | ||
|
|
a3c904dcc9 | ||
|
|
11447c9bff | ||
|
|
f9d399172c | ||
|
|
4ccff13f9c | ||
|
|
dbbdbe618f | ||
|
|
b09cff40ae | ||
|
|
df9369762a | ||
|
|
87ad14cb2a | ||
|
|
5a2ce61e2e | ||
|
|
4a7c07e754 | ||
|
|
8eb3e1e160 | ||
|
|
5bc68894b8 | ||
|
|
21890afa80 | ||
|
|
84b7200178 | ||
|
|
f8272baf07 | ||
|
|
9a6b934421 | ||
|
|
b3247d90a6 | ||
|
|
0c31f60bb0 | ||
|
|
be64c55901 | ||
|
|
b19199a35d | ||
|
|
6e67107170 | ||
|
|
71c631891f | ||
|
|
888a5ad6f6 | ||
|
|
d99b992e27 | ||
|
|
7dc67be1ad | ||
|
|
d528e9a45e | ||
|
|
2a61b54096 | ||
|
|
5b004f7565 | ||
|
|
5e915756d5 | ||
|
|
bfc66148ea | ||
|
|
2fa78133c8 | ||
|
|
3a60500d54 | ||
|
|
a5b313ab13 | ||
|
|
c37c087c65 | ||
|
|
dc573c7e9e | ||
|
|
68e1c5624a | ||
|
|
acf4ae79cb | ||
|
|
4a794a354f | ||
|
|
2953c6cb0a | ||
|
|
3812e119c0 | ||
|
|
4c9a706204 | ||
|
|
a589df7bca | ||
|
|
eb36e53057 | ||
|
|
7a87fbfa77 | ||
|
|
a703c6fb58 | ||
|
|
d3cc7a3a6f | ||
|
|
96f300ea74 | ||
|
|
a243262748 | ||
|
|
b5d43688b9 | ||
|
|
3819f07d9d | ||
|
|
81ee0de71c | ||
|
|
4444c28b9f | ||
|
|
3899b1cc10 | ||
|
|
49dbba624f | ||
|
|
309267b73c | ||
|
|
d6933321dc | ||
|
|
272c7c0980 | ||
|
|
29a333721a | ||
|
|
a86af454cc | ||
|
|
33e72a6796 | ||
|
|
3961f15dc6 | ||
|
|
faf05aa086 |
@@ -7,5 +7,5 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["www", "playground", "**-template"]
|
||||
"ignore": ["www", "**-template"]
|
||||
}
|
||||
|
||||
@@ -10,16 +10,22 @@
|
||||
"plugins": ["tailwindcss"],
|
||||
"rules": {
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"react/jsx-key": "off",
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
"tailwindcss/classnames-order": "error"
|
||||
},
|
||||
"settings": {
|
||||
"tailwindcss": {
|
||||
"callees": ["cn"]
|
||||
"callees": ["cn", "cva"],
|
||||
"config": "tailwind.config.cjs"
|
||||
},
|
||||
"next": {
|
||||
"rootDir": ["apps/*/"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3
.github/workflows/code-check.yml
vendored
3
.github/workflows/code-check.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -57,6 +58,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -94,6 +96,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
6
.github/workflows/prerelease-comment.yml
vendored
6
.github/workflows/prerelease-comment.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
for (const artifact of allArtifacts.data.artifacts) {
|
||||
// Extract the PR number and package version from the artifact name
|
||||
const match = /^npm-package-@shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||
const match = /^npm-package-shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||
|
||||
if (match) {
|
||||
require("fs").appendFileSync(
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
A new prerelease is available for testing:
|
||||
|
||||
```sh
|
||||
pnpm @shadcn/ui@${{ env.BETA_PACKAGE_VERSION }}
|
||||
npx shadcn-ui@${{ env.BETA_PACKAGE_VERSION }}
|
||||
```
|
||||
|
||||
- name: "Remove the autorelease label once published"
|
||||
@@ -60,6 +60,6 @@ jobs:
|
||||
github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ env.WORKFLOW_RUN_PR }},
|
||||
issue_number: '${{ env.WORKFLOW_RUN_PR }}',
|
||||
name: '🚀 autorelease',
|
||||
});
|
||||
|
||||
4
.github/workflows/prerelease.yml
vendored
4
.github/workflows/prerelease.yml
vendored
@@ -24,6 +24,8 @@ jobs:
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8.6.1
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
@@ -54,5 +56,5 @@ jobs:
|
||||
- name: Upload packaged artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: npm-package-@shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
name: npm-package-shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||
path: packages/cli/dist/index.js
|
||||
|
||||
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -20,18 +20,21 @@ jobs:
|
||||
|
||||
- name: Use PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8.6.1
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
version: 8.6.1
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Check for errors
|
||||
run: pnpm check
|
||||
# - name: Check for errors
|
||||
# run: pnpm check
|
||||
|
||||
- name: Build the package
|
||||
run: pnpm build:cli
|
||||
|
||||
42
.github/workflows/test.yml
vendored
Normal file
42
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- run: pnpm test
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
@@ -3,4 +3,4 @@ node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
apps/www/pages/api/components.json
|
||||
apps/www/pages/api/registry.json
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -2,5 +2,9 @@
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "packages/*/" }
|
||||
],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
}
|
||||
|
||||
37
README.md
37
README.md
@@ -4,42 +4,9 @@ Accessible and customizable components that you can copy and paste into your app
|
||||
|
||||

|
||||
|
||||
## Roadmap
|
||||
## Documentation
|
||||
|
||||
> **Warning**
|
||||
> This is work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn).
|
||||
|
||||
- [ ] Toggle Group
|
||||
- [ ] Toolbar
|
||||
- [x] ~Toast~
|
||||
- [x] ~Toggle~
|
||||
- [x] ~Navigation Menu~
|
||||
- [x] ~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 `tailwind-merge`
|
||||
- Animation with `tailwindcss-animate`
|
||||
- Conditional classes with `clsx`
|
||||
- Variants with `class-variance-authority`
|
||||
- Automatic class sorting with `eslint-plugin-tailwindcss`
|
||||
Visit http://ui.shadcn.com/docs to view the documentation.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# App
|
||||
# -----------------------------------------------------------------------------
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3001
|
||||
|
||||
@@ -2,4 +2,5 @@ dist
|
||||
node_modules
|
||||
.next
|
||||
build
|
||||
.contentlayer
|
||||
.contentlayer
|
||||
__registry__/index.tsx
|
||||
1
apps/www/__registry__/.autogenerated
Normal file
1
apps/www/__registry__/.autogenerated
Normal file
@@ -0,0 +1 @@
|
||||
// The content of this directory is autogenerated by the registry server.
|
||||
1
apps/www/__registry__/README.md
Normal file
1
apps/www/__registry__/README.md
Normal file
@@ -0,0 +1 @@
|
||||
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
||||
2012
apps/www/__registry__/index.tsx
Normal file
2012
apps/www/__registry__/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
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" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -2,15 +2,21 @@ import { notFound } from "next/navigation"
|
||||
import { allDocs } from "contentlayer/generated"
|
||||
|
||||
import "@/styles/mdx.css"
|
||||
import type { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
import Balancer from "react-wrap-balancer"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { getTableOfContents } from "@/lib/toc"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Mdx } from "@/components/mdx"
|
||||
import { DocsPageHeader } from "@/components/page-header"
|
||||
import { Mdx } from "@/components/mdx-components"
|
||||
import { DocsPager } from "@/components/pager"
|
||||
import { DashboardTableOfContents } from "@/components/toc"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { badgeVariants } from "@/registry/new-york/ui/badge"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
|
||||
interface DocPageProps {
|
||||
params: {
|
||||
@@ -18,6 +24,53 @@ interface DocPageProps {
|
||||
}
|
||||
}
|
||||
|
||||
async function getDocFromParams({ params }: DocPageProps) {
|
||||
const slug = params.slug?.join("/") || ""
|
||||
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
|
||||
|
||||
if (!doc) {
|
||||
null
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: DocPageProps): Promise<Metadata> {
|
||||
const doc = await getDocFromParams({ params })
|
||||
|
||||
if (!doc) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
openGraph: {
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
type: "article",
|
||||
url: absoluteUrl(doc.slug),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
images: [siteConfig.ogImage],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateStaticParams(): Promise<
|
||||
DocPageProps["params"][]
|
||||
> {
|
||||
@@ -27,8 +80,7 @@ export async function generateStaticParams(): Promise<
|
||||
}
|
||||
|
||||
export default async function DocPage({ params }: DocPageProps) {
|
||||
const slug = params?.slug?.join("/") || ""
|
||||
const doc = allDocs.find((doc) => doc.slugAsParams === slug)
|
||||
const doc = await getDocFromParams({ params })
|
||||
|
||||
if (!doc) {
|
||||
notFound()
|
||||
@@ -37,44 +89,64 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
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]">
|
||||
<main className="relative py-6 lg:gap-10 lg:py-8 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" />
|
||||
<div className="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
Docs
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
<div className="font-medium text-foreground">{doc.title}</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className={cn("scroll-m-20 text-4xl font-bold tracking-tight")}>
|
||||
{doc.title}
|
||||
</h1>
|
||||
{doc.description && (
|
||||
<p className="text-lg text-muted-foreground">
|
||||
<Balancer>{doc.description}</Balancer>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{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={cn(badgeVariants({ variant: "secondary" }))}
|
||||
>
|
||||
<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={cn(badgeVariants({ variant: "secondary" }))}
|
||||
>
|
||||
API Reference
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pb-12 pt-8">
|
||||
<Mdx code={doc.body.code} />
|
||||
</div>
|
||||
<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} />
|
||||
{doc.toc && (
|
||||
<div className="hidden text-sm xl:block">
|
||||
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] overflow-hidden pt-6">
|
||||
<ScrollArea className="pb-10">
|
||||
<DashboardTableOfContents toc={toc} />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { DocsSidebarNav } from "@/components/sidebar-nav"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
|
||||
interface DocsLayoutProps {
|
||||
children: React.ReactNode
|
||||
@@ -8,13 +8,15 @@ interface DocsLayoutProps {
|
||||
|
||||
export default function DocsLayout({ children }: DocsLayoutProps) {
|
||||
return (
|
||||
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-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 className="border-b">
|
||||
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
|
||||
<ScrollArea className="h-full py-6 pl-8 pr-6 lg:py-8">
|
||||
<DocsSidebarNav items={docsConfig.sidebarNav} />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
|
||||
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false)
|
||||
|
||||
async function onSubmit(event: React.SyntheticEvent) {
|
||||
event.preventDefault()
|
||||
setIsLoading(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-6", className)} {...props}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-1">
|
||||
<Label className="sr-only" htmlFor="email">
|
||||
Email
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
type="email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading && (
|
||||
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Sign In with Email
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Or continue with
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" type="button" disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Icons.gitHub className="mr-2 h-4 w-4" />
|
||||
)}{" "}
|
||||
Github
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
93
apps/www/app/examples/authentication/page.tsx
Normal file
93
apps/www/app/examples/authentication/page.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Command } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { UserAuthForm } from "@/app/examples/authentication/components/user-auth-form"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Authentication",
|
||||
description: "Authentication forms built using the components.",
|
||||
}
|
||||
|
||||
export default function AuthenticationPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/authentication-light.png"
|
||||
width={1280}
|
||||
height={843}
|
||||
alt="Authentication"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/authentication-dark.png"
|
||||
width={1280}
|
||||
height={843}
|
||||
alt="Authentication"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="container relative hidden h-[800px] flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||
<Link
|
||||
href="/examples/authentication"
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"absolute right-4 top-4 md:right-8 md:top-8"
|
||||
)}
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<div className="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex">
|
||||
<div className="absolute inset-0 bg-zinc-900" />
|
||||
<div className="relative z-20 flex items-center text-lg font-medium">
|
||||
<Command className="mr-2 h-6 w-6" /> Acme Inc
|
||||
</div>
|
||||
<div className="relative z-20 mt-auto">
|
||||
<blockquote className="space-y-2">
|
||||
<p className="text-lg">
|
||||
“This library has saved me countless hours of work and
|
||||
helped me deliver stunning designs to my clients faster than
|
||||
ever before.”
|
||||
</p>
|
||||
<footer className="text-sm">Sofia Davis</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-8">
|
||||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div className="flex flex-col space-y-2 text-center">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Create an account
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enter your email below to create your account
|
||||
</p>
|
||||
</div>
|
||||
<UserAuthForm />
|
||||
<p className="px-8 text-center text-sm text-muted-foreground">
|
||||
By clicking continue, you agree to our{" "}
|
||||
<Link
|
||||
href="/terms"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
60
apps/www/app/examples/cards/components/cookie-settings.tsx
Normal file
60
apps/www/app/examples/cards/components/cookie-settings.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Switch } from "@/registry/new-york/ui/switch"
|
||||
|
||||
export function DemoCookieSettings() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Cookie Settings</CardTitle>
|
||||
<CardDescription>Manage your cookie settings here.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="necessary" className="flex flex-col space-y-1">
|
||||
<span>Strictly Necessary</span>
|
||||
<span className="font-normal leading-snug text-muted-foreground">
|
||||
These cookies are essential in order to use the website and use
|
||||
its features.
|
||||
</span>
|
||||
</Label>
|
||||
<Switch id="necessary" defaultChecked />
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="functional" className="flex flex-col space-y-1">
|
||||
<span>Functional Cookies</span>
|
||||
<span className="font-normal leading-snug text-muted-foreground">
|
||||
These cookies allow the website to provide personalized
|
||||
functionality.
|
||||
</span>
|
||||
</Label>
|
||||
<Switch id="functional" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Label htmlFor="performance" className="flex flex-col space-y-1">
|
||||
<span>Performance Cookies</span>
|
||||
<span className="font-normal leading-snug text-muted-foreground">
|
||||
These cookies help to improve the performance of the website.
|
||||
</span>
|
||||
</Label>
|
||||
<Switch id="performance" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button variant="outline" className="w-full">
|
||||
Save preferences
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
60
apps/www/app/examples/cards/components/create-account.tsx
Normal file
60
apps/www/app/examples/cards/components/create-account.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client"
|
||||
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
|
||||
export function DemoCreateAccount() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl">Create an account</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to create your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<Button variant="outline">
|
||||
<Icons.gitHub className="mr-2 h-4 w-4" />
|
||||
Github
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<Icons.google className="mr-2 h-4 w-4" />
|
||||
Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Or continue with
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" type="email" placeholder="m@example.com" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input id="password" type="password" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full">Create account</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
18
apps/www/app/examples/cards/components/date-picker.tsx
Normal file
18
apps/www/app/examples/cards/components/date-picker.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import DatePickerWithRange from "@/registry/default/example/date-picker-with-range"
|
||||
import { Card, CardContent } from "@/registry/new-york/ui/card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
|
||||
export function DemoDatePicker() {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date" className="shrink-0">
|
||||
Pick a date
|
||||
</Label>
|
||||
<DatePickerWithRange className="[&>button]:w-[260px]" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
81
apps/www/app/examples/cards/components/github-card.tsx
Normal file
81
apps/www/app/examples/cards/components/github-card.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { ChevronDown, Circle, Plus, Star } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
|
||||
export function DemoGithub() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
|
||||
<div className="space-y-1">
|
||||
<CardTitle>shadcn/ui</CardTitle>
|
||||
<CardDescription>
|
||||
Beautifully designed components built with Radix UI and Tailwind
|
||||
CSS.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1 rounded-md bg-secondary text-secondary-foreground">
|
||||
<Button variant="secondary" className="px-3">
|
||||
<Star className="mr-2 h-4 w-4" />
|
||||
Star
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="h-[20px]" />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" className="px-2">
|
||||
<ChevronDown className="h-4 w-4 text-secondary-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
alignOffset={-5}
|
||||
className="w-[200px]"
|
||||
forceMount
|
||||
>
|
||||
<DropdownMenuLabel>Suggested Lists</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem checked>
|
||||
Future Ideas
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>My Stack</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>Inspiration</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Plus className="mr-2 h-4 w-4" /> Create List
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex space-x-4 text-sm text-muted-foreground">
|
||||
<div className="flex items-center">
|
||||
<Circle className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
|
||||
TypeScript
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Star className="mr-1 h-3 w-3" />
|
||||
20k
|
||||
</div>
|
||||
<div>Updated April 2023</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
51
apps/www/app/examples/cards/components/notifications.tsx
Normal file
51
apps/www/app/examples/cards/components/notifications.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { AtSign, Bell, BellOff } from "lucide-react"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
|
||||
export function DemoNotifications() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>
|
||||
Choose what you want to be notified about.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-1">
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
|
||||
<Bell className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Everything</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Email digest, mentions & all activity.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md bg-accent p-2 text-accent-foreground transition-all">
|
||||
<AtSign className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Available</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Only mentions and comments.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
|
||||
<BellOff className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Ignoring</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Turn off all notifications.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
117
apps/www/app/examples/cards/components/payment-method.tsx
Normal file
117
apps/www/app/examples/cards/components/payment-method.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { CreditCard } from "lucide-react"
|
||||
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
|
||||
export function DemoPaymentMethod() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Payment Method</CardTitle>
|
||||
<CardDescription>
|
||||
Add a new payment method to your account.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6">
|
||||
<RadioGroup defaultValue="card" className="grid grid-cols-3 gap-4">
|
||||
<Label
|
||||
htmlFor="card"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<RadioGroupItem value="card" id="card" className="sr-only" />
|
||||
<CreditCard className="mb-3 h-6 w-6" />
|
||||
Card
|
||||
</Label>
|
||||
<Label
|
||||
htmlFor="paypal"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<RadioGroupItem value="paypal" id="paypal" className="sr-only" />
|
||||
<Icons.paypal className="mb-3 h-6 w-6" />
|
||||
Paypal
|
||||
</Label>
|
||||
<Label
|
||||
htmlFor="apple"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<RadioGroupItem value="apple" id="apple" className="sr-only" />
|
||||
<Icons.apple className="mb-3 h-6 w-6" />
|
||||
Apple
|
||||
</Label>
|
||||
</RadioGroup>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" placeholder="First Last" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="number">Card number</Label>
|
||||
<Input id="number" placeholder="" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="month">Expires</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="month">
|
||||
<SelectValue placeholder="Month" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">January</SelectItem>
|
||||
<SelectItem value="2">February</SelectItem>
|
||||
<SelectItem value="3">March</SelectItem>
|
||||
<SelectItem value="4">April</SelectItem>
|
||||
<SelectItem value="5">May</SelectItem>
|
||||
<SelectItem value="6">June</SelectItem>
|
||||
<SelectItem value="7">July</SelectItem>
|
||||
<SelectItem value="8">August</SelectItem>
|
||||
<SelectItem value="9">September</SelectItem>
|
||||
<SelectItem value="10">October</SelectItem>
|
||||
<SelectItem value="11">November</SelectItem>
|
||||
<SelectItem value="12">December</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="year">Year</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="year">
|
||||
<SelectValue placeholder="Year" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 10 }, (_, i) => (
|
||||
<SelectItem key={i} value={`${new Date().getFullYear() + i}`}>
|
||||
{new Date().getFullYear() + i}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="cvc">CVC</Label>
|
||||
<Input id="cvc" placeholder="CVC" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full">Continue</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
82
apps/www/app/examples/cards/components/report-an-issue.tsx
Normal file
82
apps/www/app/examples/cards/components/report-an-issue.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
import { Textarea } from "@/registry/new-york/ui/textarea"
|
||||
|
||||
export function DemoReportAnIssue() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Report an issue</CardTitle>
|
||||
<CardDescription>
|
||||
What area are you having problems with?
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="area">Area</Label>
|
||||
<Select defaultValue="billing">
|
||||
<SelectTrigger id="area">
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="team">Team</SelectItem>
|
||||
<SelectItem value="billing">Billing</SelectItem>
|
||||
<SelectItem value="account">Account</SelectItem>
|
||||
<SelectItem value="deployments">Deployments</SelectItem>
|
||||
<SelectItem value="support">Support</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="security-level">Security Level</Label>
|
||||
<Select defaultValue="2">
|
||||
<SelectTrigger id="security-level">
|
||||
<SelectValue placeholder="Select level" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Severity 1 (Highest)</SelectItem>
|
||||
<SelectItem value="2">Severity 2</SelectItem>
|
||||
<SelectItem value="3">Severity 3</SelectItem>
|
||||
<SelectItem value="4">Severity 4 (Lowest)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="subject">Subject</Label>
|
||||
<Input id="subject" placeholder="I need help with..." />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
placeholder="Please include all information relevant to your issue."
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-between space-x-2">
|
||||
<Button variant="ghost">Cancel</Button>
|
||||
<Button>Submit</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
120
apps/www/app/examples/cards/components/share-document.tsx
Normal file
120
apps/www/app/examples/cards/components/share-document.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
|
||||
export function DemoShareDocument() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Share this document</CardTitle>
|
||||
<CardDescription>
|
||||
Anyone with the link can view this document.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex space-x-2">
|
||||
<Input value="http://example.com/link/to/document" readOnly />
|
||||
<Button variant="secondary" className="shrink-0">
|
||||
Copy Link
|
||||
</Button>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-medium">People with access</h4>
|
||||
<div className="grid gap-6">
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatars/03.png" />
|
||||
<AvatarFallback>OM</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
Olivia Martin
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">m@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<Select defaultValue="edit">
|
||||
<SelectTrigger className="ml-auto w-[110px]">
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="edit">Can edit</SelectItem>
|
||||
<SelectItem value="view">Can view</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatars/05.png" />
|
||||
<AvatarFallback>IN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
Isabella Nguyen
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">b@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<Select defaultValue="view">
|
||||
<SelectTrigger className="ml-auto w-[110px]">
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="edit">Can edit</SelectItem>
|
||||
<SelectItem value="view">Can view</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatars/01.png" />
|
||||
<AvatarFallback>SD</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
Sofia Davis
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">p@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<Select defaultValue="view">
|
||||
<SelectTrigger className="ml-auto w-[110px]">
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="edit">Can edit</SelectItem>
|
||||
<SelectItem value="view">Can view</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
151
apps/www/app/examples/cards/components/team-members.tsx
Normal file
151
apps/www/app/examples/cards/components/team-members.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
|
||||
export function DemoTeamMembers() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Team Members</CardTitle>
|
||||
<CardDescription>
|
||||
Invite your team members to collaborate.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6">
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatars/01.png" />
|
||||
<AvatarFallback>OM</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Sofia Davis</p>
|
||||
<p className="text-sm text-muted-foreground">m@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Owner{" "}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="end">
|
||||
<Command>
|
||||
<CommandInput placeholder="Select new role..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No roles found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Viewer</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view and comment.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Developer</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view, comment and edit.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Billing</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view, comment and manage billing.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Owner</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Admin-level access to all resources.
|
||||
</p>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatars/02.png" />
|
||||
<AvatarFallback>JL</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Jackson Lee</p>
|
||||
<p className="text-sm text-muted-foreground">p@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Member{" "}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="end">
|
||||
<Command>
|
||||
<CommandInput placeholder="Select new role..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No roles found.</CommandEmpty>
|
||||
<CommandGroup className="p-1.5">
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Viewer</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view and comment.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Developer</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view, comment and edit.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Billing</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Can view, comment and manage billing.
|
||||
</p>
|
||||
</CommandItem>
|
||||
<CommandItem className="teamaspace-y-1 flex flex-col items-start px-4 py-2">
|
||||
<p>Owner</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Admin-level access to all resources.
|
||||
</p>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
93
apps/www/app/examples/cards/page.tsx
Normal file
93
apps/www/app/examples/cards/page.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { DemoCookieSettings } from "./components/cookie-settings"
|
||||
import { DemoCreateAccount } from "./components/create-account"
|
||||
import { DemoDatePicker } from "./components/date-picker"
|
||||
import { DemoGithub } from "./components/github-card"
|
||||
import { DemoNotifications } from "./components/notifications"
|
||||
import { DemoPaymentMethod } from "./components/payment-method"
|
||||
import { DemoReportAnIssue } from "./components/report-an-issue"
|
||||
import { DemoShareDocument } from "./components/share-document"
|
||||
import { DemoTeamMembers } from "./components/team-members"
|
||||
import "./styles.css"
|
||||
import Image from "next/image"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Cards",
|
||||
description: "Examples of cards built using the components.",
|
||||
}
|
||||
|
||||
function DemoContainer({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center [&>div]:w-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function CardsPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/cards-light.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/cards-dark.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden items-start justify-center gap-6 rounded-lg p-8 md:grid lg:grid-cols-2 xl:grid-cols-3">
|
||||
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
|
||||
<DemoContainer>
|
||||
<DemoCreateAccount />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoPaymentMethod />
|
||||
</DemoContainer>
|
||||
</div>
|
||||
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
|
||||
<DemoContainer>
|
||||
<DemoTeamMembers />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoShareDocument />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoDatePicker />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoNotifications />
|
||||
</DemoContainer>
|
||||
</div>
|
||||
<div className="col-span-2 grid items-start gap-6 lg:col-span-1 lg:grid-cols-2 xl:grid-cols-1">
|
||||
<DemoContainer>
|
||||
<DemoReportAnIssue />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoGithub />
|
||||
</DemoContainer>
|
||||
<DemoContainer>
|
||||
<DemoCookieSettings />
|
||||
</DemoContainer>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
63
apps/www/app/examples/cards/styles.css
Normal file
63
apps/www/app/examples/cards/styles.css
Normal file
@@ -0,0 +1,63 @@
|
||||
[data-section="cards"] {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 214 95% 93%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 224 82% 56%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 216 100% 97%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 1.2rem;
|
||||
}
|
||||
|
||||
.dark [data-section="cards"] {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 224 82% 56%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 226 57% 21%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays, format } from "date-fns"
|
||||
import { Calendar as CalendarIcon } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Calendar } from "@/registry/new-york/ui/calendar"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
|
||||
export function CalendarDateRangePicker({
|
||||
className,
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2023, 0, 20),
|
||||
to: addDays(new Date(2023, 0, 20), 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-2", className)}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[260px] justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, "LLL dd, y")} -{" "}
|
||||
{format(date.to, "LLL dd, y")}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, "LLL dd, y")
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
apps/www/app/examples/dashboard/components/main-nav.tsx
Normal file
40
apps/www/app/examples/dashboard/components/main-nav.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function MainNav({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLElement>) {
|
||||
return (
|
||||
<nav
|
||||
className={cn("flex items-center space-x-4 lg:space-x-6", className)}
|
||||
{...props}
|
||||
>
|
||||
<Link
|
||||
href="/examples/dashboard"
|
||||
className="text-sm font-medium transition-colors hover:text-primary"
|
||||
>
|
||||
Overview
|
||||
</Link>
|
||||
<Link
|
||||
href="/examples/dashboard"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
|
||||
>
|
||||
Customers
|
||||
</Link>
|
||||
<Link
|
||||
href="/examples/dashboard"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
|
||||
>
|
||||
Products
|
||||
</Link>
|
||||
<Link
|
||||
href="/examples/dashboard"
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
78
apps/www/app/examples/dashboard/components/overview.tsx
Normal file
78
apps/www/app/examples/dashboard/components/overview.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client"
|
||||
|
||||
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: "Jan",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Feb",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Mar",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Apr",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "May",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Jun",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Jul",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Aug",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Sep",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Oct",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Nov",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
name: "Dec",
|
||||
total: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
]
|
||||
|
||||
export function Overview() {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={350}>
|
||||
<BarChart data={data}>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#888888"
|
||||
fontSize={12}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Bar dataKey="total" fill="#adfa1d" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
71
apps/www/app/examples/dashboard/components/recent-sales.tsx
Normal file
71
apps/www/app/examples/dashboard/components/recent-sales.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
|
||||
export function RecentSales() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src="/avatars/01.png" alt="Avatar" />
|
||||
<AvatarFallback>OM</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Olivia Martin</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
olivia.martin@email.com
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+$1,999.00</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Avatar className="flex h-9 w-9 items-center justify-center space-y-0 border">
|
||||
<AvatarImage src="/avatars/02.png" alt="Avatar" />
|
||||
<AvatarFallback>JL</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Jackson Lee</p>
|
||||
<p className="text-sm text-muted-foreground">jackson.lee@email.com</p>
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+$39.00</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src="/avatars/03.png" alt="Avatar" />
|
||||
<AvatarFallback>IN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Isabella Nguyen</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
isabella.nguyen@email.com
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+$299.00</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src="/avatars/04.png" alt="Avatar" />
|
||||
<AvatarFallback>WK</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">William Kim</p>
|
||||
<p className="text-sm text-muted-foreground">will@email.com</p>
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+$99.00</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src="/avatars/05.png" alt="Avatar" />
|
||||
<AvatarFallback>SD</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="ml-4 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Sofia Davis</p>
|
||||
<p className="text-sm text-muted-foreground">sofia.davis@email.com</p>
|
||||
</div>
|
||||
<div className="ml-auto font-medium">+$39.00</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
13
apps/www/app/examples/dashboard/components/search.tsx
Normal file
13
apps/www/app/examples/dashboard/components/search.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
className="md:w-[100px] lg:w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
210
apps/www/app/examples/dashboard/components/team-switcher.tsx
Normal file
210
apps/www/app/examples/dashboard/components/team-switcher.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Check, ChevronsUpDown, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/registry/new-york/ui/dialog"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
|
||||
const groups = [
|
||||
{
|
||||
label: "Personal Account",
|
||||
teams: [
|
||||
{
|
||||
label: "Alicia Koch",
|
||||
value: "personal",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Teams",
|
||||
teams: [
|
||||
{
|
||||
label: "Acme Inc.",
|
||||
value: "acme-inc",
|
||||
},
|
||||
{
|
||||
label: "Monsters Inc.",
|
||||
value: "monsters",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
type Team = (typeof groups)[number]["teams"][number]
|
||||
|
||||
type PopoverTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverTrigger>
|
||||
|
||||
interface TeamSwitcherProps extends PopoverTriggerProps {}
|
||||
|
||||
export default function TeamSwitcher({ className }: TeamSwitcherProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false)
|
||||
const [selectedTeam, setSelectedTeam] = React.useState<Team>(
|
||||
groups[0].teams[0]
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
aria-label="Select a team"
|
||||
className={cn("w-[200px] justify-between", className)}
|
||||
>
|
||||
<Avatar className="mr-2 h-5 w-5">
|
||||
<AvatarImage
|
||||
src={`https://avatar.vercel.sh/${selectedTeam.value}.png`}
|
||||
alt={selectedTeam.label}
|
||||
className="grayscale"
|
||||
/>
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
{selectedTeam.label}
|
||||
<ChevronsUpDown className="ml-auto h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandInput placeholder="Search team..." />
|
||||
<CommandEmpty>No team found.</CommandEmpty>
|
||||
{groups.map((group) => (
|
||||
<CommandGroup key={group.label} heading={group.label}>
|
||||
{group.teams.map((team) => (
|
||||
<CommandItem
|
||||
key={team.value}
|
||||
onSelect={() => {
|
||||
setSelectedTeam(team)
|
||||
setOpen(false)
|
||||
}}
|
||||
className="text-sm"
|
||||
>
|
||||
<Avatar className="mr-2 h-5 w-5">
|
||||
<AvatarImage
|
||||
src={`https://avatar.vercel.sh/${team.value}.png`}
|
||||
alt={team.label}
|
||||
className="grayscale"
|
||||
/>
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
{team.label}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
selectedTeam.value === team.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
<CommandSeparator />
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
<DialogTrigger asChild>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false)
|
||||
setShowNewTeamDialog(true)
|
||||
}}
|
||||
>
|
||||
<PlusCircle className="mr-2 h-5 w-5" />
|
||||
Create Team
|
||||
</CommandItem>
|
||||
</DialogTrigger>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create team</DialogTitle>
|
||||
<DialogDescription>
|
||||
Add a new team to manage products and customers.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<div className="space-y-4 py-2 pb-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Team name</Label>
|
||||
<Input id="name" placeholder="Acme Inc." />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="plan">Subscription plan</Label>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a plan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="free">
|
||||
<span className="font-medium">Free</span> -{" "}
|
||||
<span className="text-muted-foreground">
|
||||
Trial for two weeks
|
||||
</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="pro">
|
||||
<span className="font-medium">Pro</span> -{" "}
|
||||
<span className="text-muted-foreground">
|
||||
$9/month per user
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowNewTeamDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Continue</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
71
apps/www/app/examples/dashboard/components/user-nav.tsx
Normal file
71
apps/www/app/examples/dashboard/components/user-nav.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { CreditCard, LogOut, PlusCircle, Settings, User } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
|
||||
export function UserNav() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src="/avatars/01.png" alt="@shadcn" />
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">shadcn</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
m@example.com
|
||||
</p>
|
||||
</div>
|
||||
</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>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
<span>New Team</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>Log out</span>
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
170
apps/www/app/examples/dashboard/page.tsx
Normal file
170
apps/www/app/examples/dashboard/page.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { Activity, CreditCard, DollarSign, Download, Users } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york/ui/card"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york/ui/tabs"
|
||||
import { CalendarDateRangePicker } from "@/app/examples/dashboard/components/date-range-picker"
|
||||
import { MainNav } from "@/app/examples/dashboard/components/main-nav"
|
||||
import { Overview } from "@/app/examples/dashboard/components/overview"
|
||||
import { RecentSales } from "@/app/examples/dashboard/components/recent-sales"
|
||||
import { Search } from "@/app/examples/dashboard/components/search"
|
||||
import TeamSwitcher from "@/app/examples/dashboard/components/team-switcher"
|
||||
import { UserNav } from "@/app/examples/dashboard/components/user-nav"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard",
|
||||
description: "Example dashboard app using the components.",
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/dashboard-light.png"
|
||||
width={1280}
|
||||
height={866}
|
||||
alt="Dashboard"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/dashboard-dark.png"
|
||||
width={1280}
|
||||
height={866}
|
||||
alt="Dashboard"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden flex-col md:flex">
|
||||
<div className="border-b">
|
||||
<div className="flex h-16 items-center px-4">
|
||||
<TeamSwitcher />
|
||||
<MainNav className="mx-6" />
|
||||
<div className="ml-auto flex items-center space-x-4">
|
||||
<Search />
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 space-y-4 p-8 pt-6">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CalendarDateRangePicker />
|
||||
<Button>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs defaultValue="overview" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="analytics" disabled>
|
||||
Analytics
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="reports" disabled>
|
||||
Reports
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="notifications" disabled>
|
||||
Notifications
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Total Revenue
|
||||
</CardTitle>
|
||||
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">$45,231.89</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+20.1% from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Subscriptions
|
||||
</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+2350</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+180.1% from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Sales</CardTitle>
|
||||
<CreditCard className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+12,234</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+19% from last month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Active Now
|
||||
</CardTitle>
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+573</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+201 since last hour
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||
<Card className="col-span-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Overview</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<Overview />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="col-span-3">
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Sales</CardTitle>
|
||||
<CardDescription>
|
||||
You made 265 sales this month.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RecentSales />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
219
apps/www/app/examples/forms/account/account-form.tsx
Normal file
219
apps/www/app/examples/forms/account/account-form.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { format } from "date-fns"
|
||||
import { CalendarIcon, Check, ChevronsUpDown } from "lucide-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Calendar } from "@/registry/new-york/ui/calendar"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york/ui/form"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
const languages = [
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "French", value: "fr" },
|
||||
{ label: "German", value: "de" },
|
||||
{ label: "Spanish", value: "es" },
|
||||
{ label: "Portuguese", value: "pt" },
|
||||
{ label: "Russian", value: "ru" },
|
||||
{ label: "Japanese", value: "ja" },
|
||||
{ label: "Korean", value: "ko" },
|
||||
{ label: "Chinese", value: "zh" },
|
||||
] as const
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Name must be at least 2 characters.",
|
||||
})
|
||||
.max(30, {
|
||||
message: "Name must not be longer than 30 characters.",
|
||||
}),
|
||||
dob: z.date({
|
||||
required_error: "A date of birth is required.",
|
||||
}),
|
||||
language: z.string({
|
||||
required_error: "Please select a language.",
|
||||
}),
|
||||
})
|
||||
|
||||
type AccountFormValues = z.infer<typeof accountFormSchema>
|
||||
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<AccountFormValues> = {
|
||||
// name: "Your name",
|
||||
// dob: new Date("2023-01-23"),
|
||||
}
|
||||
|
||||
export function AccountForm() {
|
||||
const form = useForm<AccountFormValues>({
|
||||
resolver: zodResolver(accountFormSchema),
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
function onSubmit(data: AccountFormValues) {
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your name" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is the name that will be displayed on your profile and in
|
||||
emails.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dob"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[240px] pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, "PPP")
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date) =>
|
||||
date > new Date() || date < new Date("1900-01-01")
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>
|
||||
Your date of birth is used to calculate your age.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="language"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Language</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-[200px] justify-between",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value
|
||||
? languages.find(
|
||||
(language) => language.value === field.value
|
||||
)?.label
|
||||
: "Select language"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search language..." />
|
||||
<CommandEmpty>No language found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{languages.map((language) => (
|
||||
<CommandItem
|
||||
value={language.value}
|
||||
key={language.value}
|
||||
onSelect={(value) => {
|
||||
form.setValue("language", value)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
language.value === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{language.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>
|
||||
This is the language that will be used in the dashboard.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Update account</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
18
apps/www/app/examples/forms/account/page.tsx
Normal file
18
apps/www/app/examples/forms/account/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { AccountForm } from "@/app/examples/forms/account/account-form"
|
||||
|
||||
export default function SettingsAccountPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Account</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Update your account settings. Set your preferred language and
|
||||
timezone.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<AccountForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
164
apps/www/app/examples/forms/appearance/appearance-form.tsx
Normal file
164
apps/www/app/examples/forms/appearance/appearance-form.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button, buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york/ui/form"
|
||||
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
font: z.enum(["inter", "manrope", "system"], {
|
||||
invalid_type_error: "Select a font",
|
||||
required_error: "Please select a font.",
|
||||
}),
|
||||
})
|
||||
|
||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>
|
||||
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<AppearanceFormValues> = {
|
||||
theme: "light",
|
||||
}
|
||||
|
||||
export function AppearanceForm() {
|
||||
const form = useForm<AppearanceFormValues>({
|
||||
resolver: zodResolver(appearanceFormSchema),
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
function onSubmit(data: AppearanceFormValues) {
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="font"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Font</FormLabel>
|
||||
<div className="relative w-max">
|
||||
<FormControl>
|
||||
<select
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"w-[200px] appearance-none bg-transparent font-normal"
|
||||
)}
|
||||
{...field}
|
||||
>
|
||||
<option value="inter">Inter</option>
|
||||
<option value="manrope">Manrope</option>
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<ChevronDown className="absolute right-3 top-3 h-4 w-4 opacity-50" />
|
||||
</div>
|
||||
<FormDescription>
|
||||
Set the font you want to use in the dashboard.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="theme"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<FormDescription>
|
||||
Select the theme for the dashboard.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid max-w-md grid-cols-2 gap-8 pt-2"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel className="[&:has([data-state=checked])>div]:border-primary">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="light" className="sr-only" />
|
||||
</FormControl>
|
||||
<div className="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
|
||||
<div className="space-y-2 rounded-sm bg-[#ecedef] p-2">
|
||||
<div className="space-y-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div className="h-2 w-[80px] rounded-lg bg-[#ecedef]" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div className="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div className="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="block w-full p-2 text-center font-normal">
|
||||
Light
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<FormLabel className="[&:has([data-state=checked])>div]:border-primary">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="dark" className="sr-only" />
|
||||
</FormControl>
|
||||
<div className="items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground">
|
||||
<div className="space-y-2 rounded-sm bg-slate-950 p-2">
|
||||
<div className="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div className="h-2 w-[80px] rounded-lg bg-slate-400" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div className="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div className="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div className="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="block w-full p-2 text-center font-normal">
|
||||
Dark
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit">Update preferences</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
18
apps/www/app/examples/forms/appearance/page.tsx
Normal file
18
apps/www/app/examples/forms/appearance/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { AppearanceForm } from "@/app/examples/forms/appearance/appearance-form"
|
||||
|
||||
export default function SettingsAppearancePage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Appearance</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Customize the appearance of the app. Automatically switch between day
|
||||
and night themes.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<AppearanceForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
44
apps/www/app/examples/forms/components/sidebar-nav.tsx
Normal file
44
apps/www/app/examples/forms/components/sidebar-nav.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
|
||||
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
||||
items: {
|
||||
href: string
|
||||
title: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={cn(
|
||||
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
pathname === item.href
|
||||
? "bg-muted hover:bg-muted"
|
||||
: "hover:bg-transparent hover:underline",
|
||||
"justify-start"
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
132
apps/www/app/examples/forms/display/display-form.tsx
Normal file
132
apps/www/app/examples/forms/display/display-form.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Checkbox } from "@/registry/new-york/ui/checkbox"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york/ui/form"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
const items = [
|
||||
{
|
||||
id: "recents",
|
||||
label: "Recents",
|
||||
},
|
||||
{
|
||||
id: "home",
|
||||
label: "Home",
|
||||
},
|
||||
{
|
||||
id: "applications",
|
||||
label: "Applications",
|
||||
},
|
||||
{
|
||||
id: "desktop",
|
||||
label: "Desktop",
|
||||
},
|
||||
{
|
||||
id: "downloads",
|
||||
label: "Downloads",
|
||||
},
|
||||
{
|
||||
id: "documents",
|
||||
label: "Documents",
|
||||
},
|
||||
] as const
|
||||
|
||||
const displayFormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: "You have to select at least one item.",
|
||||
}),
|
||||
})
|
||||
|
||||
type DisplayFormValues = z.infer<typeof displayFormSchema>
|
||||
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<DisplayFormValues> = {
|
||||
items: ["recents", "home"],
|
||||
}
|
||||
|
||||
export function DisplayForm() {
|
||||
const form = useForm<DisplayFormValues>({
|
||||
resolver: zodResolver(displayFormSchema),
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
function onSubmit(data: DisplayFormValues) {
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Sidebar</FormLabel>
|
||||
<FormDescription>
|
||||
Select the items you want to display in the sidebar.
|
||||
</FormDescription>
|
||||
</div>
|
||||
{items.map((item) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== item.id
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
{item.label}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Update display</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
17
apps/www/app/examples/forms/display/page.tsx
Normal file
17
apps/www/app/examples/forms/display/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { DisplayForm } from "@/app/examples/forms/display/display-form"
|
||||
|
||||
export default function SettingsDisplayPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Display</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Turn items on or off to control what's displayed in the app.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<DisplayForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
apps/www/app/examples/forms/layout.tsx
Normal file
75
apps/www/app/examples/forms/layout.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { SidebarNav } from "@/app/examples/forms/components/sidebar-nav"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Forms",
|
||||
description: "Advanced form example using react-hook-form and Zod.",
|
||||
}
|
||||
|
||||
const sidebarNavItems = [
|
||||
{
|
||||
title: "Profile",
|
||||
href: "/examples/forms",
|
||||
},
|
||||
{
|
||||
title: "Account",
|
||||
href: "/examples/forms/account",
|
||||
},
|
||||
{
|
||||
title: "Appearance",
|
||||
href: "/examples/forms/appearance",
|
||||
},
|
||||
{
|
||||
title: "Notifications",
|
||||
href: "/examples/forms/notifications",
|
||||
},
|
||||
{
|
||||
title: "Display",
|
||||
href: "/examples/forms/display",
|
||||
},
|
||||
]
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function SettingsLayout({ children }: SettingsLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/forms-light.png"
|
||||
width={1280}
|
||||
height={791}
|
||||
alt="Forms"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/forms-dark.png"
|
||||
width={1280}
|
||||
height={791}
|
||||
alt="Forms"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div className="space-y-0.5">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your account settings and set e-mail preferences.
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-6" />
|
||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||
<aside className="-mx-4 lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="flex-1 lg:max-w-2xl">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
222
apps/www/app/examples/forms/notifications/notifications-form.tsx
Normal file
222
apps/www/app/examples/forms/notifications/notifications-form.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Checkbox } from "@/registry/new-york/ui/checkbox"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york/ui/form"
|
||||
import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group"
|
||||
import { Switch } from "@/registry/new-york/ui/switch"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
const notificationsFormSchema = z.object({
|
||||
type: z.enum(["all", "mentions", "none"], {
|
||||
required_error: "You need to select a notification type.",
|
||||
}),
|
||||
mobile: z.boolean().default(false).optional(),
|
||||
communication_emails: z.boolean().default(false).optional(),
|
||||
social_emails: z.boolean().default(false).optional(),
|
||||
marketing_emails: z.boolean().default(false).optional(),
|
||||
security_emails: z.boolean(),
|
||||
})
|
||||
|
||||
type NotificationsFormValues = z.infer<typeof notificationsFormSchema>
|
||||
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<NotificationsFormValues> = {
|
||||
communication_emails: false,
|
||||
marketing_emails: false,
|
||||
social_emails: true,
|
||||
security_emails: true,
|
||||
}
|
||||
|
||||
export function NotificationsForm() {
|
||||
const form = useForm<NotificationsFormValues>({
|
||||
resolver: zodResolver(notificationsFormSchema),
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
function onSubmit(data: NotificationsFormValues) {
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Notify me about...</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="all" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
All new messages
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="mentions" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Direct messages and mentions
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="none" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Nothing</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="mb-4 text-lg font-medium">Email Notifications</h3>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="communication_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">
|
||||
Communication emails
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
Receive emails about your account activity.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="marketing_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">
|
||||
Marketing emails
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
Receive emails about new products, features, and more.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="social_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Social emails</FormLabel>
|
||||
<FormDescription>
|
||||
Receive emails for friend requests, follows, and more.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="security_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Security emails</FormLabel>
|
||||
<FormDescription>
|
||||
Receive emails about your account activity and security.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled
|
||||
aria-readonly
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mobile"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
Use different settings for my mobile devices
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
You can manage your mobile notifications in the{" "}
|
||||
<Link href="/examples/forms">mobile settings</Link> page.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Update notifications</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
18
apps/www/app/examples/forms/notifications/page.tsx
Normal file
18
apps/www/app/examples/forms/notifications/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { AccountForm } from "@/app/examples/forms/account/account-form"
|
||||
import { NotificationsForm } from "@/app/examples/forms/notifications/notifications-form"
|
||||
|
||||
export default function SettingsNotificationsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Notifications</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure how you receive notifications.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<NotificationsForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/www/app/examples/forms/page.tsx
Normal file
17
apps/www/app/examples/forms/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { ProfileForm } from "@/app/examples/forms/profile-form"
|
||||
|
||||
export default function SettingsProfilePage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Profile</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This is how others will see you on the site.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<ProfileForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
191
apps/www/app/examples/forms/profile-form.tsx
Normal file
191
apps/www/app/examples/forms/profile-form.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useFieldArray, useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/registry/new-york/ui/form"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
import { Textarea } from "@/registry/new-york/ui/textarea"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Username must be at least 2 characters.",
|
||||
})
|
||||
.max(30, {
|
||||
message: "Username must not be longer than 30 characters.",
|
||||
}),
|
||||
email: z
|
||||
.string({
|
||||
required_error: "Please select an email to display.",
|
||||
})
|
||||
.email(),
|
||||
bio: z.string().max(160).min(4),
|
||||
urls: z
|
||||
.array(
|
||||
z.object({
|
||||
value: z.string().url({ message: "Please enter a valid URL." }),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
|
||||
type ProfileFormValues = z.infer<typeof profileFormSchema>
|
||||
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<ProfileFormValues> = {
|
||||
bio: "I own a computer.",
|
||||
urls: [
|
||||
{ value: "https://shadcn.com" },
|
||||
{ value: "http://twitter.com/shadcn" },
|
||||
],
|
||||
}
|
||||
|
||||
export function ProfileForm() {
|
||||
const form = useForm<ProfileFormValues>({
|
||||
resolver: zodResolver(profileFormSchema),
|
||||
defaultValues,
|
||||
mode: "onChange",
|
||||
})
|
||||
|
||||
const { fields, append } = useFieldArray({
|
||||
name: "urls",
|
||||
control: form.control,
|
||||
})
|
||||
|
||||
function onSubmit(data: ProfileFormValues) {
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="shadcn" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name. It can be your real name or a
|
||||
pseudonym. You can only change this once every 30 days.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a verified email to display" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="m@example.com">m@example.com</SelectItem>
|
||||
<SelectItem value="m@google.com">m@google.com</SelectItem>
|
||||
<SelectItem value="m@support.com">m@support.com</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
You can manage verified email addresses in your{" "}
|
||||
<Link href="/examples/forms">email settings</Link>.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bio"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bio</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Tell us a little bit about yourself"
|
||||
className="resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can <span>@mention</span> other users and organizations to
|
||||
link to them.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<FormField
|
||||
control={form.control}
|
||||
key={field.id}
|
||||
name={`urls.${index}.value`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className={cn(index !== 0 && "sr-only")}>
|
||||
URLs
|
||||
</FormLabel>
|
||||
<FormDescription className={cn(index !== 0 && "sr-only")}>
|
||||
Add links to your website, blog, or social media profiles.
|
||||
</FormDescription>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
onClick={() => append({ value: "" })}
|
||||
>
|
||||
Add URL
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="submit">Update profile</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
75
apps/www/app/examples/layout.tsx
Normal file
75
apps/www/app/examples/layout.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Examples",
|
||||
description: "Check out some examples app built using the components.",
|
||||
}
|
||||
|
||||
interface ExamplesLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="container relative">
|
||||
<PageHeader className="page-header pb-8">
|
||||
<Link
|
||||
href="/docs/changelog"
|
||||
className="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
||||
>
|
||||
🎉 <Separator className="mx-2 h-4" orientation="vertical" />{" "}
|
||||
<span className="sm:hidden">Style, a new CLI and more.</span>
|
||||
<span className="hidden sm:inline">
|
||||
Introducing Style, a new CLI and more.
|
||||
</span>
|
||||
<ChevronRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Check out some examples.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">Examples</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Dashboard, cards, authentication. Some examples built using the
|
||||
components. Use this as a guide to build your own.
|
||||
</PageHeaderDescription>
|
||||
<section className="flex w-full items-center space-x-4 pb-8 pt-4 md:pb-10">
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(buttonVariants(), "rounded-[6px]")}
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
<Link
|
||||
href="/components"
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"rounded-[6px]"
|
||||
)}
|
||||
>
|
||||
Components
|
||||
</Link>
|
||||
</section>
|
||||
</PageHeader>
|
||||
<section>
|
||||
<ExamplesNav />
|
||||
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
84
apps/www/app/examples/music/components/album-artwork.tsx
Normal file
84
apps/www/app/examples/music/components/album-artwork.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import Image from "next/image"
|
||||
import { ListMusic, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { AspectRatio } from "@/registry/new-york/ui/aspect-ratio"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from "@/registry/new-york/ui/context-menu"
|
||||
|
||||
import { Album } from "../data/albums"
|
||||
import { playlists } from "../data/playlists"
|
||||
|
||||
interface AlbumArtworkProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
album: Album
|
||||
aspectRatio?: "portrait" | "square"
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
export function AlbumArtwork({
|
||||
album,
|
||||
aspectRatio = "portrait",
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
...props
|
||||
}: AlbumArtworkProps) {
|
||||
return (
|
||||
<div className={cn("space-y-3", className)} {...props}>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
<div className="overflow-hidden rounded-md">
|
||||
<Image
|
||||
src={album.cover}
|
||||
alt={album.name}
|
||||
width={width}
|
||||
height={height}
|
||||
className={cn(
|
||||
"h-auto w-auto object-cover transition-all hover:scale-105",
|
||||
aspectRatio === "portrait" ? "aspect-[3/4]" : "aspect-square"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</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-muted-foreground">{album.artist}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
178
apps/www/app/examples/music/components/menu.tsx
Normal file
178
apps/www/app/examples/music/components/menu.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Globe, Mic } from "lucide-react"
|
||||
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarLabel,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from "@/registry/new-york/ui/menubar"
|
||||
|
||||
export function Menu() {
|
||||
return (
|
||||
<Menubar className="rounded-none border-b border-none px-2 lg:px-4">
|
||||
<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</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 className="hidden md:block">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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Plus, Podcast } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/registry/new-york/ui/dialog"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
|
||||
export function PodcastEmptyPlaceholder() {
|
||||
return (
|
||||
<div className="flex h-[450px] shrink-0 items-center justify-center rounded-md border border-dashed">
|
||||
<div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
|
||||
<Podcast className="h-10 w-10 text-muted-foreground" />
|
||||
<h3 className="mt-4 text-lg font-semibold">No episodes added</h3>
|
||||
<p className="mb-4 mt-2 text-sm text-muted-foreground">
|
||||
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
|
||||
</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>
|
||||
)
|
||||
}
|
||||
95
apps/www/app/examples/music/components/sidebar.tsx
Normal file
95
apps/www/app/examples/music/components/sidebar.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
LayoutGrid,
|
||||
Library,
|
||||
ListMusic,
|
||||
Mic2,
|
||||
Music,
|
||||
Music2,
|
||||
PlayCircle,
|
||||
Radio,
|
||||
User,
|
||||
} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
|
||||
import { Playlist } from "../data/playlists"
|
||||
|
||||
interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
playlists: Playlist[]
|
||||
}
|
||||
|
||||
export function Sidebar({ className, playlists }: SidebarProps) {
|
||||
return (
|
||||
<div className={cn("pb-12", className)}>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="px-3 py-2">
|
||||
<h2 className="mb-2 px-4 text-lg font-semibold tracking-tight">
|
||||
Discover
|
||||
</h2>
|
||||
<div className="space-y-1">
|
||||
<Button variant="secondary" className="w-full justify-start">
|
||||
<PlayCircle className="mr-2 h-4 w-4" />
|
||||
Listen Now
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||
Browse
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<Radio className="mr-2 h-4 w-4" />
|
||||
Radio
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-2">
|
||||
<h2 className="mb-2 px-4 text-lg font-semibold tracking-tight">
|
||||
Library
|
||||
</h2>
|
||||
<div className="space-y-1">
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<ListMusic className="mr-2 h-4 w-4" />
|
||||
Playlists
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<Music2 className="mr-2 h-4 w-4" />
|
||||
Songs
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
Made for You
|
||||
</Button>
|
||||
<Button variant="ghost" className="w-full justify-start">
|
||||
<Mic2 className="mr-2 h-4 w-4" />
|
||||
Artists
|
||||
</Button>
|
||||
<Button variant="ghost" 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-7 text-lg font-semibold tracking-tight">
|
||||
Playlists
|
||||
</h2>
|
||||
<ScrollArea className="h-[300px] px-1">
|
||||
<div className="space-y-1 p-2">
|
||||
{playlists?.map((playlist, i) => (
|
||||
<Button
|
||||
key={`${playlist}-${i}`}
|
||||
variant="ghost"
|
||||
className="w-full justify-start font-normal"
|
||||
>
|
||||
<ListMusic className="mr-2 h-4 w-4" />
|
||||
{playlist}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
71
apps/www/app/examples/music/data/albums.ts
Normal file
71
apps/www/app/examples/music/data/albums.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
export interface Album {
|
||||
name: string
|
||||
artist: string
|
||||
cover: string
|
||||
}
|
||||
|
||||
export const listenNowAlbums: Album[] = [
|
||||
{
|
||||
name: "React Rendezvous",
|
||||
artist: "Ethan Byte",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1611348586804-61bf6c080437?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Async Awakenings",
|
||||
artist: "Nina Netcode",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "The Art of Reusability",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1528143358888-6d3c7f67bd5d?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Stateful Symphony",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
]
|
||||
|
||||
export const madeForYouAlbums: Album[] = [
|
||||
{
|
||||
name: "Thinking Components",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1615247001958-f4bc92fa6a4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Functional Fury",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1513745405825-efaf9a49315f?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "React Rendezvous",
|
||||
artist: "Ethan Byte",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1614113489855-66422ad300a4?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Stateful Symphony",
|
||||
artist: "Beth Binary",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1446185250204-f94591f7d702?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "Async Awakenings",
|
||||
artist: "Nina Netcode",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80",
|
||||
},
|
||||
{
|
||||
name: "The Art of Reusability",
|
||||
artist: "Lena Logic",
|
||||
cover:
|
||||
"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80",
|
||||
},
|
||||
]
|
||||
16
apps/www/app/examples/music/data/playlists.ts
Normal file
16
apps/www/app/examples/music/data/playlists.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export type Playlist = (typeof playlists)[number]
|
||||
|
||||
export 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",
|
||||
]
|
||||
159
apps/www/app/examples/music/page.tsx
Normal file
159
apps/www/app/examples/music/page.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { ScrollArea, ScrollBar } from "@/registry/new-york/ui/scroll-area"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york/ui/tabs"
|
||||
|
||||
import { AlbumArtwork } from "./components/album-artwork"
|
||||
import { Menu } from "./components/menu"
|
||||
import { PodcastEmptyPlaceholder } from "./components/podcast-empty-placeholder"
|
||||
import { Sidebar } from "./components/sidebar"
|
||||
import { listenNowAlbums, madeForYouAlbums } from "./data/albums"
|
||||
import { playlists } from "./data/playlists"
|
||||
import "./styles.css"
|
||||
import Image from "next/image"
|
||||
import { PlusCircle } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Music App",
|
||||
description: "Example music app using the components.",
|
||||
}
|
||||
|
||||
export default function MusicPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/music-light.png"
|
||||
width={1280}
|
||||
height={1114}
|
||||
alt="Music"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/music-dark.png"
|
||||
width={1280}
|
||||
height={1114}
|
||||
alt="Music"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<Menu />
|
||||
<div className="border-t">
|
||||
<div className="bg-background">
|
||||
<div className="grid lg:grid-cols-5">
|
||||
<Sidebar playlists={playlists} className="hidden lg:block" />
|
||||
<div className="col-span-3 lg:col-span-4 lg:border-l">
|
||||
<div className="h-full px-4 py-6 lg:px-8">
|
||||
<Tabs defaultValue="music" className="h-full space-y-6">
|
||||
<div className="space-between flex items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="music" className="relative">
|
||||
Music
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="podcasts">Podcasts</TabsTrigger>
|
||||
<TabsTrigger value="live" disabled>
|
||||
Live
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="ml-auto mr-4">
|
||||
<Button>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Add music
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent
|
||||
value="music"
|
||||
className="border-none p-0 outline-none"
|
||||
>
|
||||
<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-muted-foreground">
|
||||
Top picks for you. Updated daily.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="relative">
|
||||
<ScrollArea>
|
||||
<div className="flex space-x-4 pb-4">
|
||||
{listenNowAlbums.map((album) => (
|
||||
<AlbumArtwork
|
||||
key={album.name}
|
||||
album={album}
|
||||
className="w-[250px]"
|
||||
aspectRatio="portrait"
|
||||
width={250}
|
||||
height={330}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
</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-muted-foreground">
|
||||
Your personal playlists. Updated daily.
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="relative">
|
||||
<ScrollArea>
|
||||
<div className="flex space-x-4 pb-4">
|
||||
{madeForYouAlbums.map((album) => (
|
||||
<AlbumArtwork
|
||||
key={album.name}
|
||||
album={album}
|
||||
className="w-[150px]"
|
||||
aspectRatio="square"
|
||||
width={150}
|
||||
height={150}
|
||||
/>
|
||||
))}
|
||||
</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-muted-foreground">
|
||||
Your favorite podcasts. Updated daily.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<PodcastEmptyPlaceholder />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
65
apps/www/app/examples/music/styles.css
Normal file
65
apps/www/app/examples/music/styles.css
Normal file
@@ -0,0 +1,65 @@
|
||||
[data-section="music"] {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 243 5% 96%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 349 89% 60%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 243 11% 4%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--accent: 243 11% 4%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 349 89% 60%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark [data-section="music"] {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 349 89% 60%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -8,13 +6,13 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
} from "@/registry/new-york/ui/dialog"
|
||||
|
||||
export function CodeViewer() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="subtle">View code</Button>
|
||||
<Button variant="secondary">View code</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
@@ -27,7 +25,7 @@ export function CodeViewer() {
|
||||
<div className="grid gap-4">
|
||||
<div className="rounded-md bg-black p-6">
|
||||
<pre>
|
||||
<code className="grid gap-1 text-sm text-slate-50 [&_span]:h-4">
|
||||
<code className="grid gap-1 text-sm text-muted-foreground [&_span]:h-4">
|
||||
<span>
|
||||
<span className="text-sky-300">import</span> os
|
||||
</span>
|
||||
@@ -78,7 +76,7 @@ export function CodeViewer() {
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your API Key can be found here. You should use environment
|
||||
variables or a secret management tool to expose your key to your
|
||||
applications.
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
ClipboardCheck,
|
||||
Copy,
|
||||
CreditCard,
|
||||
EggFried,
|
||||
Fingerprint,
|
||||
HelpCircle,
|
||||
Laptop,
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
} from "@/registry/new-york/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Slider } from "@/registry/new-york/ui/slider"
|
||||
|
||||
interface MaxLengthSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
@@ -25,7 +25,7 @@ export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxlength">Maximum Length</Label>
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1,14 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
import { Model } from "@/types"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { ModelType } from "@/data/models"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
@@ -16,18 +14,20 @@ import {
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
} from "@/registry/new-york/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
|
||||
import { Model, ModelType } from "../data/models"
|
||||
|
||||
interface ModelSelectorProps extends PopoverProps {
|
||||
types: readonly ModelType[]
|
||||
@@ -77,7 +77,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
>
|
||||
<div className="grid gap-2">
|
||||
<h4 className="font-medium leading-none">{peekedModel.name}</h4>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{peekedModel.description}
|
||||
</div>
|
||||
{peekedModel.strengths ? (
|
||||
@@ -85,7 +85,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
<h5 className="text-sm font-medium leading-none">
|
||||
Strengths
|
||||
</h5>
|
||||
<ul className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<ul className="text-sm text-muted-foreground">
|
||||
{peekedModel.strengths}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -135,12 +135,12 @@ function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
useMutationObserver(ref, (mutations) => {
|
||||
const mutation = mutations.find(
|
||||
(mutation) => mutation.attributeName === "aria-selected"
|
||||
)
|
||||
|
||||
if (mutation && mutation.target?.getAttribute("aria-selected")) {
|
||||
onPeek(model)
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "attributes") {
|
||||
if (mutation.attributeName === "aria-selected") {
|
||||
onPeek(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,7 +149,7 @@ function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||
key={model.id}
|
||||
onSelect={onSelect}
|
||||
ref={ref}
|
||||
className="aria-selected:bg-slate-900 aria-selected:text-slate-50"
|
||||
className="aria-selected:bg-primary aria-selected:text-primary-foreground"
|
||||
>
|
||||
{model.name}
|
||||
<Check
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { Dialog } from "@radix-ui/react-dialog"
|
||||
import { Flag, MoreHorizontal, Trash } from "lucide-react"
|
||||
|
||||
@@ -13,24 +12,25 @@ import {
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
} from "@/registry/new-york/ui/alert-dialog"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
} from "@/registry/new-york/ui/dialog"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Switch } from "@/registry/new-york/ui/switch"
|
||||
import { toast } from "@/registry/new-york/ui/use-toast"
|
||||
|
||||
export function PresetActions() {
|
||||
const [open, setIsOpen] = React.useState(false)
|
||||
@@ -40,7 +40,7 @@ export function PresetActions() {
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="subtle">
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Actions</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -71,7 +71,7 @@ export function PresetActions() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-6">
|
||||
<h4 className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<h4 className="text-sm text-muted-foreground">
|
||||
Playground Warnings
|
||||
</h4>
|
||||
<div className="flex items-start justify-between space-x-4 pt-3">
|
||||
@@ -80,7 +80,7 @@ export function PresetActions() {
|
||||
<span className="font-semibold">
|
||||
Show a warning when content is flagged
|
||||
</span>
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
A warning will be shown when sexual, hateful, violent or
|
||||
self-harm content is detected.
|
||||
</span>
|
||||
@@ -88,7 +88,7 @@ export function PresetActions() {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="subtle" onClick={() => setIsOpen(false)}>
|
||||
<Button variant="secondary" onClick={() => setIsOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -1,6 +1,4 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -9,15 +7,15 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
} from "@/registry/new-york/ui/dialog"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
|
||||
export function PresetSave() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="subtle">Save</Button>
|
||||
<Button variant="secondary">Save</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[475px]">
|
||||
<DialogHeader>
|
||||
@@ -1,26 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Preset } from "@/types"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command"
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
|
||||
import { Preset } from "../data/presets"
|
||||
|
||||
interface PresetSelectorProps extends PopoverProps {
|
||||
presets: Preset[]
|
||||
@@ -39,7 +39,7 @@ export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
|
||||
role="combobox"
|
||||
aria-label="Load a preset..."
|
||||
aria-expanded={open}
|
||||
className="max-w-[300px] flex-1 justify-between"
|
||||
className="flex-1 justify-between md:max-w-[200px] lg:max-w-[300px]"
|
||||
>
|
||||
{selectedPreset ? selectedPreset.name : "Load a preset..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
@@ -1,38 +1,24 @@
|
||||
"use client"
|
||||
import { Copy } from "lucide-react"
|
||||
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Clipboard, Copy } from "lucide-react"
|
||||
|
||||
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"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
|
||||
export function PresetShare() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="subtle">Share</Button>
|
||||
<Button variant="secondary">Share</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-[520px]">
|
||||
<div className="flex flex-col space-y-2 text-center sm:text-left">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
Share preset
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">
|
||||
<h3 className="text-lg font-semibold">Share preset</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Anyone who has this link and an OpenAI account will be able to view
|
||||
this.
|
||||
</p>
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
} from "@/registry/new-york/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Slider } from "@/registry/new-york/ui/slider"
|
||||
|
||||
interface TemperatureSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
@@ -27,7 +27,7 @@ export function TemperatureSelector({
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
} from "@/registry/new-york/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Slider } from "@/registry/new-york/ui/slider"
|
||||
|
||||
interface TopPSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
@@ -25,7 +25,7 @@ export function TopPSelector({ defaultValue }: TopPSelectorProps) {
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="top-p">Top P</Label>
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-slate-600 hover:border-slate-100 dark:text-slate-400 dark:hover:border-slate-800">
|
||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Model } from "@/types"
|
||||
|
||||
export const types = ["GPT-3", "Codex"] as const
|
||||
|
||||
export type ModelType = typeof types[number]
|
||||
export type ModelType = (typeof types)[number]
|
||||
|
||||
export interface Model<Type = string> {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
strengths?: string
|
||||
type: Type
|
||||
}
|
||||
|
||||
export const models: Model<ModelType>[] = [
|
||||
{
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Preset } from "@/types"
|
||||
export interface Preset {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const presets: Preset[] = [
|
||||
{
|
||||
183
apps/www/app/examples/playground/page.tsx
Normal file
183
apps/www/app/examples/playground/page.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Metadata } from "next"
|
||||
import { History } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york/ui/label"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york/ui/tabs"
|
||||
import { Textarea } from "@/registry/new-york/ui/textarea"
|
||||
|
||||
import { CodeViewer } from "./components/code-viewer"
|
||||
import { Icons } from "./components/icons"
|
||||
import { MaxLengthSelector } from "./components/maxlength-selector"
|
||||
import { ModelSelector } from "./components/model-selector"
|
||||
import { PresetActions } from "./components/preset-actions"
|
||||
import { PresetSave } from "./components/preset-save"
|
||||
import { PresetSelector } from "./components/preset-selector"
|
||||
import { PresetShare } from "./components/preset-share"
|
||||
import { TemperatureSelector } from "./components/temperature-selector"
|
||||
import { TopPSelector } from "./components/top-p-selector"
|
||||
import { models, types } from "./data/models"
|
||||
import { presets } from "./data/presets"
|
||||
import "./styles.css"
|
||||
import Image from "next/image"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Playground",
|
||||
description: "The OpenAI Playground built using the components.",
|
||||
}
|
||||
|
||||
export default function PlaygroundPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/playground-light.png"
|
||||
width={1280}
|
||||
height={916}
|
||||
alt="Playground"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/playground-dark.png"
|
||||
width={1280}
|
||||
height={916}
|
||||
alt="Playground"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden h-full flex-col md:flex">
|
||||
<div className="container flex flex-col items-start justify-between space-y-2 py-4 sm:flex-row sm:items-center sm:space-y-0 md:h-16">
|
||||
<h2 className="text-lg font-semibold">Playground</h2>
|
||||
<div className="ml-auto flex w-full space-x-2 sm:justify-end">
|
||||
<PresetSelector presets={presets} />
|
||||
<PresetSave />
|
||||
<div className="hidden space-x-2 md:flex">
|
||||
<CodeViewer />
|
||||
<PresetShare />
|
||||
</div>
|
||||
<PresetActions />
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<Tabs defaultValue="complete" className="flex-1">
|
||||
<div className="container h-full py-6">
|
||||
<div className="grid h-full items-stretch gap-6 md:grid-cols-[1fr_200px]">
|
||||
<div className="hidden flex-col space-y-4 sm:flex md:order-2">
|
||||
<div className="grid gap-2">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<span className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
Mode
|
||||
</span>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[320px] text-sm" side="left">
|
||||
Choose the interface that best suits your task. You can
|
||||
provide: a simple prompt to complete, starting and ending
|
||||
text to insert a completion within, or some text with
|
||||
instructions to edit it.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<TabsList className="grid grid-cols-3">
|
||||
<TabsTrigger value="complete">
|
||||
<span className="sr-only">Complete</span>
|
||||
<Icons.completeMode className="h-5 w-5" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="insert">
|
||||
<span className="sr-only">Insert</span>
|
||||
<Icons.insertMode className="h-5 w-5" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="edit">
|
||||
<span className="sr-only">Edit</span>
|
||||
<Icons.editMode className="h-5 w-5" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<ModelSelector types={types} models={models} />
|
||||
<TemperatureSelector defaultValue={[0.56]} />
|
||||
<MaxLengthSelector defaultValue={[256]} />
|
||||
<TopPSelector defaultValue={[0.9]} />
|
||||
</div>
|
||||
<div className="md:order-1">
|
||||
<TabsContent value="complete" className="mt-0 border-0 p-0">
|
||||
<div className="flex h-full flex-col space-y-4">
|
||||
<Textarea
|
||||
placeholder="Write a tagline for an ice cream shop"
|
||||
className="min-h-[400px] flex-1 p-4 md:min-h-[700px] lg:min-h-[700px]"
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="insert" className="mt-0 border-0 p-0">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="grid h-full grid-rows-2 gap-6 lg:grid-cols-2 lg:grid-rows-1">
|
||||
<Textarea
|
||||
placeholder="We're writing to [inset]. Congrats from OpenAI!"
|
||||
className="h-full min-h-[300px] lg:min-h-[700px] xl:min-h-[700px]"
|
||||
/>
|
||||
<div className="rounded-md border bg-muted"></div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="edit" className="mt-0 border-0 p-0">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="grid h-full gap-6 lg:grid-cols-2">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex flex-1 flex-col space-y-2">
|
||||
<Label htmlFor="input">Input</Label>
|
||||
<Textarea
|
||||
id="input"
|
||||
placeholder="We is going to the market."
|
||||
className="flex-1 lg:min-h-[580px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label htmlFor="instructions">Instructions</Label>
|
||||
<Textarea
|
||||
id="instructions"
|
||||
placeholder="Fix the grammar."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[21px] min-h-[400px] rounded-md border bg-muted lg:min-h-[700px]" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<History className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
65
apps/www/app/examples/playground/styles.css
Normal file
65
apps/www/app/examples/playground/styles.css
Normal file
@@ -0,0 +1,65 @@
|
||||
[data-section="playground"] {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 154 79% 96%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 143 72% 29%;
|
||||
--primary-foreground: 141 75% 97%;
|
||||
|
||||
--secondary: 145 80% 10%;
|
||||
--secondary-foreground: 141 75% 97%;
|
||||
|
||||
--accent: 154 79% 96%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 158 64% 52%;
|
||||
|
||||
--radius: 0rem;
|
||||
}
|
||||
|
||||
.dark [data-section="playground"] {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 143 72% 29%;
|
||||
--primary-foreground: 141 75% 97%;
|
||||
|
||||
--secondary: 145 80% 10%;
|
||||
--secondary-foreground: 141 75% 97%;
|
||||
|
||||
--accent: 154 79% 96%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 158 64% 52%;
|
||||
|
||||
--radius: 0rem;
|
||||
}
|
||||
120
apps/www/app/examples/tasks/components/columns.tsx
Normal file
120
apps/www/app/examples/tasks/components/columns.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
"use client"
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
import { Badge } from "@/registry/new-york/ui/badge"
|
||||
import { Checkbox } from "@/registry/new-york/ui/checkbox"
|
||||
|
||||
import { labels, priorities, statuses } from "../data/data"
|
||||
import { Task } from "../data/schema"
|
||||
import { DataTableColumnHeader } from "./data-table-column-header"
|
||||
import { DataTableRowActions } from "./data-table-row-actions"
|
||||
|
||||
export const columns: ColumnDef<Task>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Task" />
|
||||
),
|
||||
cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Title" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const label = labels.find((label) => label.value === row.original.label)
|
||||
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
{label && <Badge variant="outline">{label.label}</Badge>}
|
||||
<span className="max-w-[500px] truncate font-medium">
|
||||
{row.getValue("title")}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Status" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const status = statuses.find(
|
||||
(status) => status.value === row.getValue("status")
|
||||
)
|
||||
|
||||
if (!status) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-[100px] items-center">
|
||||
{status.icon && (
|
||||
<status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{status.label}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "priority",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Priority" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const priority = priorities.find(
|
||||
(priority) => priority.value === row.getValue("priority")
|
||||
)
|
||||
|
||||
if (!priority) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{priority.icon && (
|
||||
<priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{priority.label}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => <DataTableRowActions row={row} />,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { ChevronsUpDown, EyeOff, SortAsc, SortDesc } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
column: Column<TData, TValue>
|
||||
title: string
|
||||
}
|
||||
|
||||
export function DataTableColumnHeader<TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
className,
|
||||
}: DataTableColumnHeaderProps<TData, TValue>) {
|
||||
if (!column.getCanSort()) {
|
||||
return <div className={cn(className)}>{title}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center space-x-2", className)}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||
>
|
||||
<span>{title}</span>
|
||||
{column.getIsSorted() === "desc" ? (
|
||||
<SortDesc className="ml-2 h-4 w-4" />
|
||||
) : column.getIsSorted() === "asc" ? (
|
||||
<SortAsc className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
||||
<SortAsc className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Asc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
||||
<SortDesc className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Desc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
||||
<EyeOff className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Hide
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import * as React from "react"
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { Check, LucideIcon, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Badge } from "@/registry/new-york/ui/badge"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york/ui/popover"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
|
||||
interface DataTableFacetedFilter<TData, TValue> {
|
||||
column?: Column<TData, TValue>
|
||||
title?: string
|
||||
options: {
|
||||
label: string
|
||||
value: string
|
||||
icon?: LucideIcon
|
||||
}[]
|
||||
}
|
||||
|
||||
export function DataTableFacetedFilter<TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
options,
|
||||
}: DataTableFacetedFilter<TData, TValue>) {
|
||||
const facets = column?.getFacetedUniqueValues()
|
||||
const selectedValues = new Set(column?.getFilterValue() as string[])
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-8 border-dashed">
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
{title}
|
||||
{selectedValues?.size > 0 && (
|
||||
<>
|
||||
<Separator orientation="vertical" className="mx-2 h-4" />
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="rounded-sm px-1 font-normal lg:hidden"
|
||||
>
|
||||
{selectedValues.size}
|
||||
</Badge>
|
||||
<div className="hidden space-x-1 lg:flex">
|
||||
{selectedValues.size > 2 ? (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="rounded-sm px-1 font-normal"
|
||||
>
|
||||
{selectedValues.size} selected
|
||||
</Badge>
|
||||
) : (
|
||||
options
|
||||
.filter((option) => selectedValues.has(option.value))
|
||||
.map((option) => (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
key={option.value}
|
||||
className="rounded-sm px-1 font-normal"
|
||||
>
|
||||
{option.label}
|
||||
</Badge>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder={title} />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.has(option.value)
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
onSelect={() => {
|
||||
if (isSelected) {
|
||||
selectedValues.delete(option.value)
|
||||
} else {
|
||||
selectedValues.add(option.value)
|
||||
}
|
||||
const filterValues = Array.from(selectedValues)
|
||||
column?.setFilterValue(
|
||||
filterValues.length ? filterValues : undefined
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
isSelected
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<Check className={cn("h-4 w-4")} />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
{facets?.get(option.value) && (
|
||||
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
|
||||
{facets.get(option.value)}
|
||||
</span>
|
||||
)}
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
{selectedValues.size > 0 && (
|
||||
<>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
onSelect={() => column?.setFilterValue(undefined)}
|
||||
className="justify-center text-center"
|
||||
>
|
||||
Clear filters
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york/ui/select"
|
||||
|
||||
interface DataTablePaginationProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
export function DataTablePagination<TData>({
|
||||
table,
|
||||
}: DataTablePaginationProps<TData>) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm font-medium">Rows per page</p>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value))
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||
{pageSize}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<ChevronsLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<ChevronsRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { Row } from "@tanstack/react-table"
|
||||
import { Copy, MoreHorizontal, Pen, Star, Tags, Trash } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
|
||||
import { labels } from "../data/data"
|
||||
import { taskSchema } from "../data/schema"
|
||||
|
||||
interface DataTableRowActionsProps<TData> {
|
||||
row: Row<TData>
|
||||
}
|
||||
|
||||
export function DataTableRowActions<TData>({
|
||||
row,
|
||||
}: DataTableRowActionsProps<TData>) {
|
||||
const task = taskSchema.parse(row.original)
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[160px]">
|
||||
<DropdownMenuItem>
|
||||
<Pen className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Copy className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Make a copy
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Star className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Favorite
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Tags className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Labels
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup value={task.label}>
|
||||
{labels.map((label) => (
|
||||
<DropdownMenuRadioItem key={label.value} value={label.value}>
|
||||
{label.label}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
import { DataTableViewOptions } from "@/app/examples/tasks/components/data-table-view-options"
|
||||
|
||||
import { priorities, statuses } from "../data/data"
|
||||
import { DataTableFacetedFilter } from "./data-table-faceted-filter"
|
||||
|
||||
interface DataTableToolbarProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
export function DataTableToolbar<TData>({
|
||||
table,
|
||||
}: DataTableToolbarProps<TData>) {
|
||||
const isFiltered = table.getState().columnFilters.length > 0
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
value={(table.getColumn("title")?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) =>
|
||||
table.getColumn("title")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="h-8 w-[150px] lg:w-[250px]"
|
||||
/>
|
||||
{table.getColumn("status") && (
|
||||
<DataTableFacetedFilter
|
||||
column={table.getColumn("status")}
|
||||
title="Status"
|
||||
options={statuses}
|
||||
/>
|
||||
)}
|
||||
{table.getColumn("priority") && (
|
||||
<DataTableFacetedFilter
|
||||
column={table.getColumn("priority")}
|
||||
title="Priority"
|
||||
options={priorities}
|
||||
/>
|
||||
)}
|
||||
{isFiltered && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => table.resetColumnFilters()}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset
|
||||
<X className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DataTableViewOptions table={table} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { SlidersHorizontal } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
|
||||
interface DataTableViewOptionsProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
export function DataTableViewOptions<TData>({
|
||||
table,
|
||||
}: DataTableViewOptionsProps<TData>) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-auto hidden h-8 lg:flex"
|
||||
>
|
||||
<SlidersHorizontal className="mr-2 h-4 w-4" />
|
||||
View
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[150px]">
|
||||
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter(
|
||||
(column) =>
|
||||
typeof column.accessorFn !== "undefined" && column.getCanHide()
|
||||
)
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
126
apps/www/app/examples/tasks/components/data-table.tsx
Normal file
126
apps/www/app/examples/tasks/components/data-table.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york/ui/table"
|
||||
|
||||
import { DataTablePagination } from "../components/data-table-pagination"
|
||||
import { DataTableToolbar } from "../components/data-table-toolbar"
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({})
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
columnFilters,
|
||||
},
|
||||
enableRowSelection: true,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<DataTableToolbar table={table} />
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<DataTablePagination table={table} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
71
apps/www/app/examples/tasks/components/user-nav.tsx
Normal file
71
apps/www/app/examples/tasks/components/user-nav.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { CreditCard, LogOut, PlusCircle, Settings, User } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york/ui/avatar"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york/ui/dropdown-menu"
|
||||
|
||||
export function UserNav() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">shadcn</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
m@example.com
|
||||
</p>
|
||||
</div>
|
||||
</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>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
<span>New Team</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>Log out</span>
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
71
apps/www/app/examples/tasks/data/data.tsx
Normal file
71
apps/www/app/examples/tasks/data/data.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
ArrowDownToLine,
|
||||
ArrowRightToLine,
|
||||
ArrowUpCircle,
|
||||
ArrowUpToLine,
|
||||
CheckCircle2,
|
||||
Circle,
|
||||
HelpCircle,
|
||||
XCircle,
|
||||
} from "lucide-react"
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
value: "bug",
|
||||
label: "Bug",
|
||||
},
|
||||
{
|
||||
value: "feature",
|
||||
label: "Feature",
|
||||
},
|
||||
{
|
||||
value: "documentation",
|
||||
label: "Documentation",
|
||||
},
|
||||
]
|
||||
|
||||
export const statuses = [
|
||||
{
|
||||
value: "backlog",
|
||||
label: "Backlog",
|
||||
icon: HelpCircle,
|
||||
},
|
||||
{
|
||||
value: "todo",
|
||||
label: "Todo",
|
||||
icon: Circle,
|
||||
},
|
||||
{
|
||||
value: "in progress",
|
||||
label: "In Progress",
|
||||
icon: ArrowUpCircle,
|
||||
},
|
||||
{
|
||||
value: "done",
|
||||
label: "Done",
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
{
|
||||
value: "canceled",
|
||||
label: "Canceled",
|
||||
icon: XCircle,
|
||||
},
|
||||
]
|
||||
|
||||
export const priorities = [
|
||||
{
|
||||
label: "Low",
|
||||
value: "low",
|
||||
icon: ArrowDownToLine,
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium",
|
||||
icon: ArrowRightToLine,
|
||||
},
|
||||
{
|
||||
label: "High",
|
||||
value: "high",
|
||||
icon: ArrowUpToLine,
|
||||
},
|
||||
]
|
||||
13
apps/www/app/examples/tasks/data/schema.ts
Normal file
13
apps/www/app/examples/tasks/data/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod"
|
||||
|
||||
// We're keeping a simple non-relational schema here.
|
||||
// IRL, you will have a schema for your data models.
|
||||
export const taskSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
status: z.string(),
|
||||
label: z.string(),
|
||||
priority: z.string(),
|
||||
})
|
||||
|
||||
export type Task = z.infer<typeof taskSchema>
|
||||
20
apps/www/app/examples/tasks/data/seed.ts
Executable file
20
apps/www/app/examples/tasks/data/seed.ts
Executable file
@@ -0,0 +1,20 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { faker } from "@faker-js/faker"
|
||||
|
||||
import { labels, priorities, statuses } from "./data"
|
||||
|
||||
const tasks = Array.from({ length: 100 }, () => ({
|
||||
id: `TASK-${faker.datatype.number({ min: 1000, max: 9999 })}`,
|
||||
title: faker.hacker.phrase().replace(/^./, (letter) => letter.toUpperCase()),
|
||||
status: faker.helpers.arrayElement(statuses).value,
|
||||
label: faker.helpers.arrayElement(labels).value,
|
||||
priority: faker.helpers.arrayElement(priorities).value,
|
||||
}))
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, "tasks.json"),
|
||||
JSON.stringify(tasks, null, 2)
|
||||
)
|
||||
|
||||
console.log("✅ Tasks data generated.")
|
||||
702
apps/www/app/examples/tasks/data/tasks.json
Normal file
702
apps/www/app/examples/tasks/data/tasks.json
Normal file
@@ -0,0 +1,702 @@
|
||||
[
|
||||
{
|
||||
"id": "TASK-8782",
|
||||
"title": "You can't compress the program without quantifying the open-source SSD pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7878",
|
||||
"title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7839",
|
||||
"title": "We need to bypass the neural TCP card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5562",
|
||||
"title": "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8686",
|
||||
"title": "I'll parse the wireless SSL protocol, that should driver the API panel!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1280",
|
||||
"title": "Use the digital TLS panel, then you can transmit the haptic system!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7262",
|
||||
"title": "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1138",
|
||||
"title": "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7184",
|
||||
"title": "We need to program the back-end THX pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5160",
|
||||
"title": "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5618",
|
||||
"title": "Generating the driver won't do anything, we need to index the online SSL application!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6699",
|
||||
"title": "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2858",
|
||||
"title": "We need to override the online UDP bus!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9864",
|
||||
"title": "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8404",
|
||||
"title": "We need to generate the virtual HEX alarm!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5365",
|
||||
"title": "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1780",
|
||||
"title": "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6938",
|
||||
"title": "Use the redundant SCSI application, then you can hack the optical alarm!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9885",
|
||||
"title": "We need to compress the auxiliary VGA driver!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3216",
|
||||
"title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9285",
|
||||
"title": "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1024",
|
||||
"title": "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7068",
|
||||
"title": "You can't generate the capacitor without indexing the wireless HEX pixel!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6502",
|
||||
"title": "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5326",
|
||||
"title": "We need to hack the redundant UTF8 transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6274",
|
||||
"title": "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1571",
|
||||
"title": "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9518",
|
||||
"title": "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5581",
|
||||
"title": "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2197",
|
||||
"title": "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8484",
|
||||
"title": "We need to parse the solid state UDP firewall!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9892",
|
||||
"title": "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9616",
|
||||
"title": "We need to synthesize the cross-platform ASCII pixel!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9744",
|
||||
"title": "Use the back-end IP card, then you can input the solid state hard drive!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1376",
|
||||
"title": "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7382",
|
||||
"title": "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2290",
|
||||
"title": "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1533",
|
||||
"title": "You can't input the firewall without overriding the wireless TCP firewall!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4920",
|
||||
"title": "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5168",
|
||||
"title": "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7103",
|
||||
"title": "We need to parse the multi-byte EXE bandwidth!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4314",
|
||||
"title": "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3415",
|
||||
"title": "Use the cross-platform XML application, then you can quantify the solid state feed!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8339",
|
||||
"title": "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6995",
|
||||
"title": "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8053",
|
||||
"title": "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4336",
|
||||
"title": "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8790",
|
||||
"title": "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8980",
|
||||
"title": "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7342",
|
||||
"title": "Use the neural CLI card, then you can parse the online port!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5608",
|
||||
"title": "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1606",
|
||||
"title": "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7872",
|
||||
"title": "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4167",
|
||||
"title": "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9581",
|
||||
"title": "You can't index the port without hacking the cross-platform XSS monitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8806",
|
||||
"title": "We need to bypass the back-end SSL panel!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6542",
|
||||
"title": "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6806",
|
||||
"title": "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9549",
|
||||
"title": "You can't bypass the bus without connecting the neural JBOD bus!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1075",
|
||||
"title": "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1427",
|
||||
"title": "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1907",
|
||||
"title": "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4309",
|
||||
"title": "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3973",
|
||||
"title": "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7962",
|
||||
"title": "Use the wireless RAM program, then you can hack the cross-platform feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3360",
|
||||
"title": "You can't quantify the program without synthesizing the neural OCR interface!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9887",
|
||||
"title": "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3649",
|
||||
"title": "I'll input the virtual USB system, that should circuit the DNS monitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3586",
|
||||
"title": "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5150",
|
||||
"title": "I'll hack the wireless XSS port, that should transmitter the IP interface!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3652",
|
||||
"title": "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6884",
|
||||
"title": "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1591",
|
||||
"title": "We need to connect the mobile XSS driver!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3802",
|
||||
"title": "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7253",
|
||||
"title": "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9739",
|
||||
"title": "We need to hack the multi-byte HDD bus!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4424",
|
||||
"title": "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3922",
|
||||
"title": "You can't back up the capacitor without generating the wireless PCI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4921",
|
||||
"title": "I'll index the open-source IP feed, that should system the GB application!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5814",
|
||||
"title": "We need to calculate the 1080p AGP feed!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2645",
|
||||
"title": "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4535",
|
||||
"title": "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4463",
|
||||
"title": "We need to copy the solid state AGP monitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9745",
|
||||
"title": "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2080",
|
||||
"title": "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3838",
|
||||
"title": "I'll bypass the online TCP application, that should panel the AGP system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1340",
|
||||
"title": "We need to navigate the virtual PNG circuit!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6665",
|
||||
"title": "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7585",
|
||||
"title": "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6319",
|
||||
"title": "We need to copy the multi-byte SCSI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4369",
|
||||
"title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9035",
|
||||
"title": "We need to override the solid state PNG array!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3970",
|
||||
"title": "You can't index the transmitter without quantifying the haptic ASCII card!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4473",
|
||||
"title": "You can't bypass the protocol without overriding the neural RSS program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4136",
|
||||
"title": "You can't hack the hard drive without hacking the primary JSON program!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3939",
|
||||
"title": "Use the back-end SQL firewall, then you can connect the neural hard drive!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2007",
|
||||
"title": "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7516",
|
||||
"title": "Use the primary SQL program, then you can generate the auxiliary transmitter!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6906",
|
||||
"title": "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5207",
|
||||
"title": "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
}
|
||||
]
|
||||
65
apps/www/app/examples/tasks/page.tsx
Normal file
65
apps/www/app/examples/tasks/page.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { promises as fs } from "fs"
|
||||
import path from "path"
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { z } from "zod"
|
||||
|
||||
import { columns } from "./components/columns"
|
||||
import { DataTable } from "./components/data-table"
|
||||
import { UserNav } from "./components/user-nav"
|
||||
import { taskSchema } from "./data/schema"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Tasks",
|
||||
description: "A task and issue tracker build using Tanstack Table.",
|
||||
}
|
||||
|
||||
// Simulate a database read for tasks.
|
||||
async function getTasks() {
|
||||
const data = await fs.readFile(
|
||||
path.join(process.cwd(), "app/examples/tasks/data/tasks.json")
|
||||
)
|
||||
|
||||
const tasks = JSON.parse(data.toString())
|
||||
|
||||
return z.array(taskSchema).parse(tasks)
|
||||
}
|
||||
|
||||
export default async function TaskPage() {
|
||||
const tasks = await getTasks()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/tasks-light.png"
|
||||
width={1280}
|
||||
height={998}
|
||||
alt="Playground"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/tasks-dark.png"
|
||||
width={1280}
|
||||
height={998}
|
||||
alt="Playground"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Welcome back!</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Here's a list of your tasks for this month!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable data={tasks} columns={columns} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import { Heart } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
export default function FigmaPage() {
|
||||
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]">
|
||||
Figma UI Kit. Crafted to perfectly match the Radix UI components.
|
||||
</h1>
|
||||
<p className="max-w-[750px] text-lg text-slate-700 dark:text-slate-400 sm:text-xl">
|
||||
Every component recreated in Figma. With customizable props,
|
||||
typography and icons. Open sourced by{" "}
|
||||
<Link
|
||||
href="https://twitter.com/skirano"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium underline underline-offset-4"
|
||||
>
|
||||
Pietro Schirano
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</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="https://www.figma.com/community/file/1203061493325953101"
|
||||
className={buttonVariants({ size: "lg" })}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Get a copy
|
||||
</Link>
|
||||
<Link
|
||||
href="https://twitter.com/skirano"
|
||||
className={cn(
|
||||
buttonVariants({ size: "lg", variant: "outline" }),
|
||||
"px-5"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Heart className="mr-2 h-4 w-4 fill-current" />
|
||||
Follow Pietro
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
<AspectRatio ratio={16 / 9} className="w-full">
|
||||
<iframe
|
||||
src="https://embed.figma.com/file/1203061493325953101/hf_embed?community_viewer=true&embed_host=shadcn&hub_file_id=1203061493325953101&kind=&viewer=1"
|
||||
className="h-full w-full overflow-hidden rounded-lg border border-slate-200 bg-slate-100 dark:border-slate-700 dark:bg-slate-800"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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()} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,71 @@
|
||||
import { Inter as FontSans } from "@next/font/google"
|
||||
|
||||
import "@/styles/globals.css"
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { fontSans } from "@/lib/fonts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { ThemeProvider } from "@/components/providers"
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { Toaster } from "@/components/ui/toaster"
|
||||
import { Toaster as DefaultToaster } from "@/registry/default/ui/toaster"
|
||||
import { Toaster as NewYorkToaster } from "@/registry/new-york/ui/toaster"
|
||||
|
||||
const fontSans = FontSans({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
display: "swap",
|
||||
})
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: siteConfig.name,
|
||||
template: `%s - ${siteConfig.name}`,
|
||||
},
|
||||
description: siteConfig.description,
|
||||
keywords: [
|
||||
"Next.js",
|
||||
"React",
|
||||
"Tailwind CSS",
|
||||
"Server Components",
|
||||
"Radix UI",
|
||||
],
|
||||
authors: [
|
||||
{
|
||||
name: "shadcn",
|
||||
url: "https://shadcn.com",
|
||||
},
|
||||
],
|
||||
creator: "shadcn",
|
||||
themeColor: [
|
||||
{ media: "(prefers-color-scheme: light)", color: "white" },
|
||||
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
||||
],
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: siteConfig.url,
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
siteName: siteConfig.name,
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
images: [siteConfig.ogImage],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-16x16.png",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||
}
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
@@ -26,20 +78,21 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
<head />
|
||||
<body
|
||||
className={cn(
|
||||
"min-h-screen bg-white font-sans text-slate-900 antialiased dark:bg-slate-900 dark:text-slate-50",
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
fontSans.variable
|
||||
)}
|
||||
>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<div className="relative flex min-h-screen flex-col">
|
||||
<SiteHeader />
|
||||
<div className="container flex-1">{children}</div>
|
||||
<div className="flex-1">{children}</div>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
<TailwindIndicator />
|
||||
</ThemeProvider>
|
||||
<Analytics />
|
||||
<Toaster />
|
||||
<NewYorkToaster />
|
||||
<DefaultToaster />
|
||||
</body>
|
||||
</html>
|
||||
</>
|
||||
|
||||
@@ -1,74 +1,77 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
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 { buttonVariants } from "@/components/ui/button"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import { Icons } from "@/components/icons"
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import DashboardPage from "@/app/examples/dashboard/page"
|
||||
|
||||
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-[750px] 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.{" "}
|
||||
<span className="font-semibold">
|
||||
Use this to build your own component library
|
||||
</span>
|
||||
.
|
||||
</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
|
||||
<div className="container relative">
|
||||
<PageHeader className="pb-8">
|
||||
<Link
|
||||
href="/docs/changelog"
|
||||
className="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
||||
>
|
||||
🎉 <Separator className="mx-2 h-4" orientation="vertical" />{" "}
|
||||
<span className="sm:hidden">Style, a new CLI and more.</span>
|
||||
<span className="hidden sm:inline">
|
||||
Introducing Style, a new CLI and more.
|
||||
</span>
|
||||
<ChevronRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
<PageHeaderHeading>Build your component library.</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Beautifully designed components that you can copy and paste into your
|
||||
apps. Accessible. Customizable. Open Source.
|
||||
</PageHeaderDescription>
|
||||
<div className="flex w-full items-center space-x-4 pb-8 pt-4 md:pb-10">
|
||||
<Link href="/docs" className={cn(buttonVariants())}>
|
||||
Get Started
|
||||
</Link>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={siteConfig.links.github}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline", size: "lg" }),
|
||||
"md:hidden"
|
||||
)}
|
||||
className={cn(buttonVariants({ variant: "outline" }))}
|
||||
>
|
||||
<Icons.gitHub className="mr-2 h-4 w-4" />
|
||||
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>
|
||||
</PageHeader>
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary" />
|
||||
<section className="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden">
|
||||
<Image
|
||||
src="/examples/dashboard-light.png"
|
||||
width={1280}
|
||||
height={866}
|
||||
alt="Dashboard"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/dashboard-dark.png"
|
||||
width={1280}
|
||||
height={866}
|
||||
alt="Dashboard"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</section>
|
||||
<section className="hidden md:block">
|
||||
<div className="overflow-hidden rounded-lg border bg-background shadow">
|
||||
<DashboardPage />
|
||||
</div>
|
||||
</section>
|
||||
<section className="hidden lg:block">
|
||||
<AppleMusicDemo />
|
||||
</section>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
27
apps/www/app/sink/layout.tsx
Normal file
27
apps/www/app/sink/layout.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import { styles } from "@/registry/styles"
|
||||
|
||||
interface SinkLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function SinkLayout({ children }: SinkLayoutProps) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="container">
|
||||
<div className="flex space-x-2 px-2 py-4">
|
||||
{styles.map((style) => (
|
||||
<Link href={`/sink/${style.name}`} key={style.name}>
|
||||
{style.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<ThemeWrapper>{children}</ThemeWrapper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
206
apps/www/app/sink/new-york/page.tsx
Normal file
206
apps/www/app/sink/new-york/page.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import AccordionDemo from "@/registry/new-york/example/accordion-demo"
|
||||
import AlertDialogDemo from "@/registry/new-york/example/alert-dialog-demo"
|
||||
import AspectRatioDemo from "@/registry/new-york/example/aspect-ratio-demo"
|
||||
import AvatarDemo from "@/registry/new-york/example/avatar-demo"
|
||||
import BadgeDemo from "@/registry/new-york/example/badge-demo"
|
||||
import BadgeDestructive from "@/registry/new-york/example/badge-destructive"
|
||||
import BadgeOutline from "@/registry/new-york/example/badge-outline"
|
||||
import BadgeSecondary from "@/registry/new-york/example/badge-secondary"
|
||||
import ButtonDemo from "@/registry/new-york/example/button-demo"
|
||||
import ButtonDestructive from "@/registry/new-york/example/button-destructive"
|
||||
import ButtonGhost from "@/registry/new-york/example/button-ghost"
|
||||
import ButtonLink from "@/registry/new-york/example/button-link"
|
||||
import ButtonLoading from "@/registry/new-york/example/button-loading"
|
||||
import ButtonOutline from "@/registry/new-york/example/button-outline"
|
||||
import ButtonSecondary from "@/registry/new-york/example/button-secondary"
|
||||
import ButtonWithIcon from "@/registry/new-york/example/button-with-icon"
|
||||
import CardDemo from "@/registry/new-york/example/card-demo"
|
||||
import CheckboxDemo from "@/registry/new-york/example/checkbox-demo"
|
||||
import CollapsibleDemo from "@/registry/new-york/example/collapsible-demo"
|
||||
import CommandDemo from "@/registry/new-york/example/command-demo"
|
||||
import ContextMenuDemo from "@/registry/new-york/example/context-menu-demo"
|
||||
import DatePickerDemo from "@/registry/new-york/example/date-picker-demo"
|
||||
import DialogDemo from "@/registry/new-york/example/dialog-demo"
|
||||
import DropdownMenuDemo from "@/registry/new-york/example/dropdown-menu-demo"
|
||||
import HoverCardDemo from "@/registry/new-york/example/hover-card-demo"
|
||||
import MenubarDemo from "@/registry/new-york/example/menubar-demo"
|
||||
import NavigationMenuDemo from "@/registry/new-york/example/navigation-menu-demo"
|
||||
import PopoverDemo from "@/registry/new-york/example/popover-demo"
|
||||
import ProgressDemo from "@/registry/new-york/example/progress-demo"
|
||||
import RadioGroupDemo from "@/registry/new-york/example/radio-group-demo"
|
||||
import ScrollAreaDemo from "@/registry/new-york/example/scroll-area-demo"
|
||||
import SelectDemo from "@/registry/new-york/example/select-demo"
|
||||
import SeparatorDemo from "@/registry/new-york/example/separator-demo"
|
||||
import SheetDemo from "@/registry/new-york/example/sheet-demo"
|
||||
import SkeletonDemo from "@/registry/new-york/example/skeleton-demo"
|
||||
import SliderDemo from "@/registry/new-york/example/slider-demo"
|
||||
import SwitchDemo from "@/registry/new-york/example/switch-demo"
|
||||
import TabsDemo from "@/registry/new-york/example/tabs-demo"
|
||||
import ToastDemo from "@/registry/new-york/example/toast-demo"
|
||||
import ToggleDemo from "@/registry/new-york/example/toggle-demo"
|
||||
import ToggleDisabled from "@/registry/new-york/example/toggle-disabled"
|
||||
import ToggleOutline from "@/registry/new-york/example/toggle-outline"
|
||||
import ToggleWithText from "@/registry/new-york/example/toggle-with-text"
|
||||
import TooltipDemo from "@/registry/new-york/example/tooltip-demo"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export default function KitchenSinkPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid grid-cols-3 items-start gap-4">
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SliderDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
className="spa flex-col items-start space-x-0
|
||||
space-y-2"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">Documentation</p>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
You can customize the theme using{" "}
|
||||
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-foreground">
|
||||
CSS variables
|
||||
</code>
|
||||
.{" "}
|
||||
<Link
|
||||
href="#"
|
||||
className="font-medium text-primary underline underline-offset-4"
|
||||
>
|
||||
Click here
|
||||
</Link>{" "}
|
||||
to learn more.
|
||||
</p>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CheckboxDemo />
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<MenubarDemo />
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="flex-col items-start space-x-0 space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<ButtonDemo />
|
||||
<ButtonSecondary />
|
||||
<ButtonDestructive />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonOutline />
|
||||
<ButtonLink />
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonWithIcon />
|
||||
<ButtonLoading />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&_ul>li:last-child]:hidden">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="justify-between">
|
||||
<SwitchDemo />
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<PopoverDemo />
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<TooltipDemo />
|
||||
<SheetDemo />
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>span]:h-[80px] [&>span]:w-[200px]">
|
||||
<RadioGroupDemo />
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<DropdownMenuDemo />
|
||||
<AlertDialogDemo />
|
||||
<DialogDemo />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<BadgeDemo />
|
||||
<BadgeSecondary />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<ToggleDemo />
|
||||
<ToggleOutline />
|
||||
<ToggleDisabled />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ComponentWrapper({
|
||||
className,
|
||||
children,
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between space-x-4 rounded-md p-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
206
apps/www/app/sink/page.tsx
Normal file
206
apps/www/app/sink/page.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import AccordionDemo from "@/registry/default/example/accordion-demo"
|
||||
import AlertDialogDemo from "@/registry/default/example/alert-dialog-demo"
|
||||
import AspectRatioDemo from "@/registry/default/example/aspect-ratio-demo"
|
||||
import AvatarDemo from "@/registry/default/example/avatar-demo"
|
||||
import BadgeDemo from "@/registry/default/example/badge-demo"
|
||||
import BadgeDestructive from "@/registry/default/example/badge-destructive"
|
||||
import BadgeOutline from "@/registry/default/example/badge-outline"
|
||||
import BadgeSecondary from "@/registry/default/example/badge-secondary"
|
||||
import ButtonDemo from "@/registry/default/example/button-demo"
|
||||
import ButtonDestructive from "@/registry/default/example/button-destructive"
|
||||
import ButtonGhost from "@/registry/default/example/button-ghost"
|
||||
import ButtonLink from "@/registry/default/example/button-link"
|
||||
import ButtonLoading from "@/registry/default/example/button-loading"
|
||||
import ButtonOutline from "@/registry/default/example/button-outline"
|
||||
import ButtonSecondary from "@/registry/default/example/button-secondary"
|
||||
import ButtonWithIcon from "@/registry/default/example/button-with-icon"
|
||||
import CardDemo from "@/registry/default/example/card-demo"
|
||||
import CheckboxDemo from "@/registry/default/example/checkbox-demo"
|
||||
import CollapsibleDemo from "@/registry/default/example/collapsible-demo"
|
||||
import CommandDemo from "@/registry/default/example/command-demo"
|
||||
import ContextMenuDemo from "@/registry/default/example/context-menu-demo"
|
||||
import DatePickerDemo from "@/registry/default/example/date-picker-demo"
|
||||
import DialogDemo from "@/registry/default/example/dialog-demo"
|
||||
import DropdownMenuDemo from "@/registry/default/example/dropdown-menu-demo"
|
||||
import HoverCardDemo from "@/registry/default/example/hover-card-demo"
|
||||
import MenubarDemo from "@/registry/default/example/menubar-demo"
|
||||
import NavigationMenuDemo from "@/registry/default/example/navigation-menu-demo"
|
||||
import PopoverDemo from "@/registry/default/example/popover-demo"
|
||||
import ProgressDemo from "@/registry/default/example/progress-demo"
|
||||
import RadioGroupDemo from "@/registry/default/example/radio-group-demo"
|
||||
import ScrollAreaDemo from "@/registry/default/example/scroll-area-demo"
|
||||
import SelectDemo from "@/registry/default/example/select-demo"
|
||||
import SeparatorDemo from "@/registry/default/example/separator-demo"
|
||||
import SheetDemo from "@/registry/default/example/sheet-demo"
|
||||
import SkeletonDemo from "@/registry/default/example/skeleton-demo"
|
||||
import SliderDemo from "@/registry/default/example/slider-demo"
|
||||
import SwitchDemo from "@/registry/default/example/switch-demo"
|
||||
import TabsDemo from "@/registry/default/example/tabs-demo"
|
||||
import ToastDemo from "@/registry/default/example/toast-demo"
|
||||
import ToggleDemo from "@/registry/default/example/toggle-demo"
|
||||
import ToggleDisabled from "@/registry/default/example/toggle-disabled"
|
||||
import ToggleOutline from "@/registry/default/example/toggle-outline"
|
||||
import ToggleWithText from "@/registry/default/example/toggle-with-text"
|
||||
import TooltipDemo from "@/registry/default/example/tooltip-demo"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
|
||||
export default function KitchenSinkPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid grid-cols-3 items-start gap-4">
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SliderDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
className="spa flex-col items-start space-x-0
|
||||
space-y-2"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">Documentation</p>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
You can customize the theme using{" "}
|
||||
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-foreground">
|
||||
CSS variables
|
||||
</code>
|
||||
.{" "}
|
||||
<Link
|
||||
href="#"
|
||||
className="font-medium text-primary underline underline-offset-4"
|
||||
>
|
||||
Click here
|
||||
</Link>{" "}
|
||||
to learn more.
|
||||
</p>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CheckboxDemo />
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<MenubarDemo />
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="flex-col items-start space-x-0 space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<ButtonDemo />
|
||||
<ButtonSecondary />
|
||||
<ButtonDestructive />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonOutline />
|
||||
<ButtonLink />
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonWithIcon />
|
||||
<ButtonLoading />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&_ul>li:last-child]:hidden">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="justify-between">
|
||||
<SwitchDemo />
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<PopoverDemo />
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<TooltipDemo />
|
||||
<SheetDemo />
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>span]:h-[80px] [&>span]:w-[200px]">
|
||||
<RadioGroupDemo />
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<DropdownMenuDemo />
|
||||
<AlertDialogDemo />
|
||||
<DialogDemo />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<BadgeDemo />
|
||||
<BadgeSecondary />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<ToggleDemo />
|
||||
<ToggleOutline />
|
||||
<ToggleDisabled />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ComponentWrapper({
|
||||
className,
|
||||
children,
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between space-x-4 rounded-md p-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
BIN
apps/www/assets/fonts/CalSans-SemiBold.ttf
Normal file
BIN
apps/www/assets/fonts/CalSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/www/assets/fonts/CalSans-SemiBold.woff
Normal file
BIN
apps/www/assets/fonts/CalSans-SemiBold.woff
Normal file
Binary file not shown.
BIN
apps/www/assets/fonts/CalSans-SemiBold.woff2
Normal file
BIN
apps/www/assets/fonts/CalSans-SemiBold.woff2
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user