Compare commits

..

56 Commits

Author SHA1 Message Date
github-actions[bot]
df9369762a chore(release): version packages (#352)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-05-13 13:35:46 +04:00
Nirmalya Ghosh
87ad14cb2a fix(shadcn-ui): add missing deps for Button (#259)
* fix: add missing deps for Button

* fix: use correct dep

Co-authored-by: Pablo <its.monotype@gmail.com>

* chore: add changeset

---------

Co-authored-by: Pablo <its.monotype@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-05-13 13:07:39 +04:00
Jack
5a2ce61e2e fix(data-table): uncontrolled input error on data-table (#335) 2023-05-11 10:07:39 +04:00
Bilal Afzal
4a7c07e754 fix(next-template): remove unused import Laptop in icons.tsx (#310)
Remove unused import 'Laptop' in icons.tsx (nextjs-example)

Co-authored-by: shadcn <m@shadcn.com>
2023-05-10 21:48:46 +04:00
shadcn
8eb3e1e160 fix(www): update padding for row actions 2023-05-10 20:51:00 +04:00
shadcn
5bc68894b8 fix(www): filter display for lg 2023-05-10 16:32:57 +04:00
Connor Stevens
21890afa80 docs(www): fix typo
Co-authored-by: shadcn <m@shadcn.com>
2023-05-10 07:49:17 +04:00
Ben Jacobson
84b7200178 docs(www): fix link to DataTable (#322) 2023-05-10 00:09:15 +04:00
shadcn
f8272baf07 feat: add table and data table (#321) 2023-05-09 23:25:26 +04:00
Mike Robinson
9a6b934421 feat(table): add table component (#248)
* Add table.tsx

* Update table.tsx

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-08 15:49:19 +04:00
Mike Robinson
b3247d90a6 fix(checkbox,radio-group): Increase contrast ratio of checkboxes and radio buttons (#224)
* Update checkbox.tsx

* Update radio-group.tsx

* fix(radio-group): update colors for contrast

* fix(checkbox): update colors for contrast

---------

Co-authored-by: shadcn <m@shadcn.com>
2023-05-08 15:43:10 +04:00
Danazumi
0c31f60bb0 fix(next-template): strict type check
Co-authored-by: Danazumi <granzin98@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2023-05-03 18:47:49 +04:00
James
be64c55901 fix(dropdown-menu): dropdown sub menu using wrong text color (#285) 2023-05-03 17:20:18 +04:00
shadcn
b19199a35d docs: move combobox and date picker to their own page (#283) 2023-05-03 15:59:00 +04:00
shadcn
6e67107170 fix(www): update to canary with scrolling fix (#279) 2023-05-03 10:53:59 +04:00
github-actions[bot]
71c631891f chore(release): version packages (#272)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-05-02 19:25:02 +04:00
shadcn
888a5ad6f6 fix(shadcn-ui): update color of cards in dark mode (#271) 2023-05-02 15:39:16 +04:00
Evan Rosenfeld
d99b992e27 feat(button): support render delegation with asChild parameter (#174) 2023-04-30 12:01:41 +04:00
Elliot Anderson
7dc67be1ad fix(www): set icon fill to current currentColor (#239) 2023-04-29 18:52:25 +04:00
Aron Hafner
d528e9a45e docs(www): set correct colors for card in darkmode (#213) 2023-04-20 16:36:38 +04:00
Jack B
2a61b54096 fix(www): component selection in copy-button (#220)
Co-authored-by: shadcn <m@shadcn.com>
2023-04-20 16:20:14 +04:00
Esteban De la Rosa
5b004f7565 docs(www): Fix typo in alert component usage (#196)
Co-authored-by: shadcn <m@shadcn.com>
2023-04-20 12:49:15 +04:00
shadcn
5e915756d5 feat(www): implement event tracking (#218)
* feat(www): implement event tracking

* fix(www): always show copy button

* fix(www): update align props for copy button
2023-04-20 12:21:50 +04:00
Elia Maino
bfc66148ea fix(badge): update destructive bg color on hover (#189) 2023-04-20 09:34:32 +04:00
Vincent Taneri
2fa78133c8 docs(www): fix a typo in FAQ (#198) 2023-04-19 19:56:04 +04:00
Seo Jun Hyung
3a60500d54 fix(www): remove styles for preventing scroll when modal is open (#209) 2023-04-19 19:43:33 +04:00
shadcn
a5b313ab13 chore: update template 2023-04-19 15:01:29 +04:00
github-actions[bot]
c37c087c65 chore(release): version packages (#206)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-04-19 13:47:25 +04:00
shadcn
dc573c7e9e fix: repo name in changeset config (#205) 2023-04-19 13:38:58 +04:00
shadcn
68e1c5624a chore(www): update deps and fix CI errors (#204) 2023-04-19 13:22:53 +04:00
shadcn
acf4ae79cb docs(shadcn-ui): update README (#203) 2023-04-19 09:48:47 +04:00
shadcn
4a794a354f feat(shadcn-ui): update init message (#197) 2023-04-18 19:12:38 +04:00
shadcn
2953c6cb0a fix(shadcn-ui): support for array of dependencies (#195) 2023-04-18 18:34:25 +04:00
shadcn
3812e119c0 fix(www): color of link 2023-04-18 10:18:13 +04:00
shadcn
4c9a706204 fix(switch): update background color for dark mode 2023-04-18 10:17:00 +04:00
The Command Runner
a589df7bca docs(www): add type keyword to ClassValue (#184)
Add `type` keyword because Typescript complains, since ClassValue is only imported as a type.
2023-04-18 10:10:19 +04:00
Gimel Dick
eb36e53057 fix: typo in badge component (#186) 2023-04-18 05:45:45 +04:00
shadcn
7a87fbfa77 fix(www): background color for auth form 2023-04-17 19:41:45 +04:00
shadcn
a703c6fb58 feat: CSS variables and CLI (#180) 2023-04-17 19:19:40 +04:00
github-actions[bot]
d3cc7a3a6f chore(release): version packages (#179)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-04-17 16:55:42 +04:00
shadcn
96f300ea74 ci: skip type check temporarily (#178) 2023-04-17 15:52:17 +04:00
shadcn
a243262748 feat(shadcn-ui): init command 2023-04-17 15:41:03 +04:00
shadcn
b5d43688b9 feat(shadcn-ui): add support for aliases 2023-04-17 12:59:57 +04:00
shadcn
3819f07d9d chore: update template 2023-04-06 12:19:55 +04:00
Raj Savaliya
81ee0de71c chore: replace third party @next/font with next/font (#145) 2023-04-03 09:15:23 +04:00
Storm
4444c28b9f docs(collapsible): add missing imports for collapsible (#150) 2023-04-03 08:30:50 +04:00
Hugo Sainte-Marie
3899b1cc10 docs(dialog): update dialog (#137) 2023-04-03 08:19:47 +04:00
Hugo Sainte-Marie
49dbba624f docs(scroll-area): update description for scroll-area (#136) 2023-04-03 08:14:59 +04:00
Storm
309267b73c docs: change sheet wrong import (#141) 2023-04-01 08:06:06 +04:00
Luke McDonald
d6933321dc fix: remove extra curly brace in style element (#146) 2023-04-01 07:52:48 +04:00
shadcn
272c7c0980 feat(button): remove active:scale-95 2023-04-01 07:11:34 +04:00
shadcn
29a333721a feat: update front page 2023-04-01 06:55:03 +04:00
shadcn
a86af454cc chore: update pnpm lock 2023-04-01 06:47:39 +04:00
shadcn
33e72a6796 fix: remove dropdown from main nav 2023-04-01 06:46:14 +04:00
shadcn
3961f15dc6 fix: remove unist dependency 2023-04-01 06:46:01 +04:00
shadcn
faf05aa086 docs(www): update prod message 2023-03-08 19:27:03 +04:00
443 changed files with 17898 additions and 27954 deletions

View File

@@ -7,5 +7,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["www", "playground", "**-template"]
"ignore": ["www", "**-template"]
}

View File

@@ -16,10 +16,17 @@
},
"settings": {
"tailwindcss": {
"callees": ["cn"]
"callees": ["cn"],
"config": "tailwind.config.cjs"
},
"next": {
"rootDir": ["apps/*/"]
}
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser"
}
]
}

View File

@@ -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',
});

View File

@@ -54,5 +54,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

View File

@@ -30,8 +30,8 @@ jobs:
- 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

View File

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

View File

@@ -4,42 +4,9 @@ Accessible and customizable components that you can copy and paste into your app
![hero](apps/www/public/og.jpg)
## 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

View File

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

View File

@@ -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" }}
/>
)
}

View File

@@ -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 { badgeVariants } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
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"
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,42 +89,60 @@ 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>
<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}
<Separator className="my-4 md:my-6" />
<Mdx code={doc.body.code} />
<Separator className="my-4 md:my-6" />
<DocsPager doc={doc} />
</div>
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
<DashboardTableOfContents toc={toc} />
<div 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>
</main>

View File

@@ -1,6 +1,6 @@
import { docsConfig } from "@/config/docs"
import { DocsSidebarNav } from "@/components/sidebar-nav"
import { ScrollArea } from "@/components/ui/scroll-area"
import { DocsSidebarNav } from "@/components/sidebar-nav"
interface DocsLayoutProps {
children: React.ReactNode
@@ -8,9 +8,9 @@ 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">
<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 overflow-y-auto border-r md:sticky md:block">
<ScrollArea className="py-6 pr-6 lg:py-8">
<DocsSidebarNav items={docsConfig.sidebarNav} />
</ScrollArea>
</aside>

View File

@@ -0,0 +1,71 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Icons } from "@/components/icons"
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>
)
}

View File

@@ -0,0 +1,99 @@
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 "@/components/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", size: "sm" }),
"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-cover"
style={{
backgroundImage:
"url(https://images.unsplash.com/photo-1590069261209-f8e9b8642343?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1376&q=80)",
}}
/>
<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">
&ldquo;This library has saved me countless hours of work and
helped me deliver stunning designs to my clients faster than
ever before. Highly recommended!&rdquo;
</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>
</>
)
}

View File

@@ -0,0 +1,60 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/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>
)
}

View File

@@ -0,0 +1,60 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Icons } from "@/components/icons"
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>
)
}

View File

@@ -0,0 +1,18 @@
import { Card, CardContent } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { DatePickerWithRange } from "@/components/examples/date-picker/with-range"
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>
)
}

View File

@@ -0,0 +1,81 @@
import { ChevronDown, Circle, Plus, Star } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Separator } from "@/components/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" />
TypeScipt
</div>
<div className="flex items-center">
<Star className="mr-1 h-3 w-3" />
10k
</div>
<div>Updated April 2023</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,51 @@
import { AtSign, Bell, BellOff } from "lucide-react"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
export function DemoNotifications() {
return (
<Card>
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
Choose what you want to be notified about.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-1 p-1.5">
<div className="flex items-center space-x-4 rounded-md p-2 hover:bg-accent hover:text-accent-foreground">
<Bell className="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="flex items-center space-x-4 rounded-md bg-accent p-2 text-accent-foreground">
<AtSign className="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="flex items-center space-x-4 rounded-md p-2 hover:bg-accent hover:text-accent-foreground">
<BellOff className="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>
)
}

View File

@@ -0,0 +1,117 @@
import { CreditCard } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Icons } from "@/components/icons"
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>
)
}

View File

@@ -0,0 +1,82 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/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>
)
}

View File

@@ -0,0 +1,116 @@
"use client"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Separator } from "@/components/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>
)
}

View File

@@ -0,0 +1,147 @@
import { ChevronDown } from "lucide-react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/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>
)
}

View 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>
</>
)
}

View 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%;
}

View File

@@ -0,0 +1,66 @@
"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 "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/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"}
size="sm"
className={cn(
"w-[240px] 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>
)
}

View 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>
)
}

View 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>
)
}

View File

@@ -0,0 +1,67 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/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>
)
}

View File

@@ -0,0 +1,13 @@
import { Input } from "@/components/ui/input"
export function Search() {
return (
<div>
<Input
type="search"
placeholder="Search..."
className="h-9 md:w-[100px] lg:w-[300px]"
/>
</div>
)
}

View File

@@ -0,0 +1,205 @@
"use client"
import * as React from "react"
import { Check, ChevronsUpDown, PlusCircle } from "lucide-react"
import { cn } from "@/lib/utils"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command"
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 {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/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="ghost"
size="sm"
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}
/>
<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}
/>
<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>
)
}

View File

@@ -0,0 +1,67 @@
import { CreditCard, LogOut, PlusCircle, Settings, User } from "lucide-react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/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>
)
}

View File

@@ -0,0 +1,165 @@
import { Metadata } from "next"
import Image from "next/image"
import { Activity, CreditCard, DollarSign, Download, Users } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/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 size="sm">
<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>
</>
)
}

View File

@@ -0,0 +1,72 @@
import { Metadata } from "next"
import Link from "next/link"
import { ChevronRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { ExamplesNav } from "@/components/examples-nav"
import {
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
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 pb-10">
<PageHeader className="page-header">
<Link
href="/docs/components/data-table"
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" />{" "}
Introducing Table and Data Table{" "}
<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-xl">
{children}
</div>
</section>
</div>
</>
)
}

View File

@@ -0,0 +1,84 @@
import Image from "next/image"
import { ListMusic, PlusCircle } from "lucide-react"
import { cn } from "@/lib/utils"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/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>
)
}

View 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 "@/components/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>
)
}

View File

@@ -0,0 +1,53 @@
import { Plus, Podcast } 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"
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>
)
}

View File

@@ -0,0 +1,99 @@
import {
LayoutGrid,
Library,
ListMusic,
Mic2,
Music,
Music2,
PlayCircle,
Radio,
User,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/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-4 py-2">
<h2 className="mb-2 px-2 text-lg font-semibold tracking-tight">
Discover
</h2>
<div className="space-y-1">
<Button
variant="secondary"
size="sm"
className="w-full justify-start"
>
<PlayCircle className="mr-2 h-4 w-4" />
Listen Now
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<LayoutGrid className="mr-2 h-4 w-4" />
Browse
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<Radio className="mr-2 h-4 w-4" />
Radio
</Button>
</div>
</div>
<div className="px-4 py-2">
<h2 className="mb-2 px-2 text-lg font-semibold tracking-tight">
Library
</h2>
<div className="space-y-1">
<Button variant="ghost" size="sm" className="w-full justify-start">
<ListMusic className="mr-2 h-4 w-4" />
Playlists
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<Music2 className="mr-2 h-4 w-4" />
Songs
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<User className="mr-2 h-4 w-4" />
Made for You
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<Mic2 className="mr-2 h-4 w-4" />
Artists
</Button>
<Button variant="ghost" size="sm" className="w-full justify-start">
<Library className="mr-2 h-4 w-4" />
Albums
</Button>
</div>
</div>
<div className="py-2">
<h2 className="relative px-6 text-lg font-semibold tracking-tight">
Playlists
</h2>
<ScrollArea className="h-[300px] px-2">
<div className="space-y-1 p-2">
{playlists?.map((playlist) => (
<Button
variant="ghost"
size="sm"
className="w-full justify-start font-normal"
>
<ListMusic className="mr-2 h-4 w-4" />
{playlist}
</Button>
))}
</div>
</ScrollArea>
</div>
</div>
</div>
)
}

View 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",
},
]

View 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",
]

View File

@@ -0,0 +1,154 @@
import { Metadata } from "next"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/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 "@/components/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>
</>
)
}

View 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;
}

View File

@@ -1,5 +1,3 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -14,7 +12,7 @@ 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.

View File

@@ -7,7 +7,6 @@ import {
ClipboardCheck,
Copy,
CreditCard,
EggFried,
Fingerprint,
HelpCircle,
Laptop,

View File

@@ -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>

View File

@@ -1,13 +1,11 @@
"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 { useMutationObserver } from "@/hooks/use-mutation-observer"
import { Button } from "@/components/ui/button"
import {
Command,
@@ -29,6 +27,8 @@ import {
PopoverTrigger,
} from "@/components/ui/popover"
import { Model, ModelType } from "../data/models"
interface ModelSelectorProps extends PopoverProps {
types: readonly ModelType[]
models: Model[]
@@ -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

View File

@@ -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"
@@ -31,6 +30,7 @@ import {
} from "@/components/ui/dropdown-menu"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { toast } from "@/components/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>

View File

@@ -1,5 +1,3 @@
"use client"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -17,7 +15,7 @@ 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>

View File

@@ -1,9 +1,7 @@
"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"
@@ -22,6 +20,8 @@ import {
PopoverTrigger,
} from "@/components/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" />

View File

@@ -1,18 +1,6 @@
"use client"
import { PopoverProps } from "@radix-ui/react-popover"
import { Clipboard, Copy } from "lucide-react"
import { 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 {
@@ -25,14 +13,12 @@ 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>[] = [
{

View File

@@ -1,4 +1,7 @@
import { Preset } from "@/types"
export interface Preset {
id: string
name: string
}
export const presets: Preset[] = [
{

View File

@@ -0,0 +1,178 @@
import { Metadata } from "next"
import { History } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Textarea } from "@/components/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>
</>
)
}

View 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;
}

View File

@@ -0,0 +1,120 @@
"use client"
import { ColumnDef } from "@tanstack/react-table"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/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} />,
},
]

View File

@@ -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 "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/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>
)
}

View File

@@ -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 "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Separator } from "@/components/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>
)
}

View File

@@ -0,0 +1,97 @@
import { Table } from "@tanstack/react-table"
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/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>
)
}

View File

@@ -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 "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/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>
)
}

View File

@@ -0,0 +1,63 @@
"use client"
import { Table } from "@tanstack/react-table"
import { X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/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.getPreFilteredRowModel().rows.length >
table.getFilteredRowModel().rows.length
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>
)
}

View File

@@ -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 "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/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>
)
}

View 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 "@/components/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>
)
}

View File

@@ -0,0 +1,67 @@
import { CreditCard, LogOut, PlusCircle, Settings, User } from "lucide-react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/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>
)
}

View 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,
},
]

View 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>

View 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.")

View 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"
}
]

View 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&apos;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>
</>
)
}

View File

@@ -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>
</>
)
}

View File

@@ -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()} />
</>
)
}

View File

@@ -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 { Toaster } from "@/components/ui/toaster"
import { Analytics } from "@/components/analytics"
import { SiteFooter } from "@/components/site-footer"
import { SiteHeader } from "@/components/site-header"
import { StyleSwitcher } from "@/components/style-switcher"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/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,18 +78,19 @@ 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>
<StyleSwitcher />
<Analytics />
<Toaster />
</body>

View File

@@ -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 { Separator } from "@/components/ui/separator"
import { ExamplesNav } from "@/components/examples-nav"
import { Icons } from "@/components/icons"
import {
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
import { PromoVideo } from "@/components/promo-video"
import { StyleSwitcher } from "@/components/style-switcher"
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 pb-10">
<StyleSwitcher />
<PageHeader>
<Link
href="/docs/components/data-table"
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" />{" "}
Introducing Table and Data Table{" "}
<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-xl">
<DashboardPage />
</div>
</section>
<section className="hidden lg:block">
<AppleMusicDemo />
</section>
</>
</div>
)
}

201
apps/www/app/sink/page.tsx Normal file
View File

@@ -0,0 +1,201 @@
import * as React from "react"
import Link from "next/link"
import { cn } from "@/lib/utils"
import { AccordionDemo } from "@/components/examples/accordion/demo"
import { AlertDialogDemo } from "@/components/examples/alert-dialog/demo"
import { AspectRatioDemo } from "@/components/examples/aspect-ratio/demo"
import { AvatarDemo } from "@/components/examples/avatar/demo"
import { BadgeDemo } from "@/components/examples/badge/demo"
import { BadgeDestructive } from "@/components/examples/badge/destructive"
import { BadgeOutline } from "@/components/examples/badge/outline"
import { BadgeSecondary } from "@/components/examples/badge/secondary"
import { ButtonDemo } from "@/components/examples/button/demo"
import { ButtonDestructive } from "@/components/examples/button/destructive"
import { ButtonGhost } from "@/components/examples/button/ghost"
import { ButtonLink } from "@/components/examples/button/link"
import { ButtonLoading } from "@/components/examples/button/loading"
import { ButtonOutline } from "@/components/examples/button/outline"
import { ButtonSecondary } from "@/components/examples/button/secondary"
import { ButtonWithIcon } from "@/components/examples/button/with-icon"
import { CardDemo } from "@/components/examples/card/demo"
import { CheckboxDemo } from "@/components/examples/checkbox/demo"
import { CollapsibleDemo } from "@/components/examples/collapsible/demo"
import { CommandDemo } from "@/components/examples/command/demo"
import { ContextMenuDemo } from "@/components/examples/context-menu/demo"
import { DatePickerDemo } from "@/components/examples/date-picker/demo"
import { DialogDemo } from "@/components/examples/dialog/demo"
import { DropdownMenuDemo } from "@/components/examples/dropdown-menu/demo"
import { HoverCardDemo } from "@/components/examples/hover-card/demo"
import { MenubarDemo } from "@/components/examples/menubar/demo"
import { NavigationMenuDemo } from "@/components/examples/navigation-menu/demo"
import { PopoverDemo } from "@/components/examples/popover/demo"
import { ProgressDemo } from "@/components/examples/progress/demo"
import { RadioGroupDemo } from "@/components/examples/radio-group/demo"
import { ScrollAreaDemo } from "@/components/examples/scroll-area/demo"
import { SelectDemo } from "@/components/examples/select/demo"
import { SeparatorDemo } from "@/components/examples/separator/demo"
import { SheetDemo } from "@/components/examples/sheet/demo"
import { SkeletonDemo } from "@/components/examples/skeleton/demo"
import { SliderDemo } from "@/components/examples/slider/demo"
import { SwitchDemo } from "@/components/examples/switch/demo"
import { TabsDemo } from "@/components/examples/tabs/demo"
import { ToastDemo } from "@/components/examples/toast/demo"
import { ToggleDemo } from "@/components/examples/toggle/demo"
import { ToggleDisabled } from "@/components/examples/toggle/disabled"
import { ToggleOutline } from "@/components/examples/toggle/outline"
import { ToggleWithText } from "@/components/examples/toggle/with-text"
import { TooltipDemo } from "@/components/examples/tooltip/demo"
export default function KitchenSinkPage() {
return (
<div className="container">
<div className="grid gap-4 py-10">
<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-foreground-muted text-sm">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>
</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 border p-4",
className
)}
>
{children}
</div>
)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -40,12 +40,12 @@ export function CodeBlockWrapper({
</CollapsibleContent>
<div
className={cn(
"absolute flex items-center justify-center bg-gradient-to-b from-slate-900/30 to-slate-900/90 p-2",
isOpened ? "inset-x-0 bottom-3 h-12" : "inset-0"
"absolute flex items-center justify-center bg-gradient-to-b from-background/30 to-muted/90 p-2",
isOpened ? "inset-x-0 bottom-0 h-12" : "inset-0"
)}
>
<CollapsibleTrigger asChild>
<Button variant="subtle" className="h-8 text-xs">
<Button variant="secondary" className="h-8 text-xs">
{isOpened ? "Collapse" : expandButtonTitle}
</Button>
</CollapsibleTrigger>

View File

@@ -47,14 +47,14 @@ export function CommandMenu({ ...props }: DialogProps) {
<Button
variant="outline"
className={cn(
"relative h-9 w-full justify-start text-sm text-slate-500 dark:text-slate-400 sm:pr-12 md:w-40 lg:w-64"
"relative h-9 w-full justify-start rounded-[0.5rem] text-sm text-muted-foreground sm:pr-12 md:w-40 lg:w-64"
)}
onClick={() => setOpen(true)}
{...props}
>
<span className="hidden lg:inline-flex">Search documentation...</span>
<span className="inline-flex lg:hidden">Search...</span>
<kbd className="pointer-events-none absolute top-2 right-1.5 hidden h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400 sm:flex">
<kbd className="pointer-events-none absolute right-1.5 top-2 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
<span className="text-xs"></span>K
</kbd>
</Button>
@@ -77,23 +77,23 @@ export function CommandMenu({ ...props }: DialogProps) {
</CommandItem>
))}
</CommandGroup>
<CommandGroup heading="Components">
{allDocs
.filter((doc) => doc.component)
.map((doc) => (
{docsConfig.sidebarNav.map((group) => (
<CommandGroup key={group.title} heading={group.title}>
{group.items.map((navItem) => (
<CommandItem
key={doc._id}
key={navItem.href}
onSelect={() => {
runCommand(() => router.push(doc.slug))
runCommand(() => router.push(navItem.href as string))
}}
>
<div className="mr-2 flex h-4 w-4 items-center justify-center">
<Circle className="h-3 w-3" />
</div>
{doc.title}
{navItem.title}
</CommandItem>
))}
</CommandGroup>
</CommandGroup>
))}
<CommandSeparator />
<CommandGroup heading="Theme">
<CommandItem onSelect={() => runCommand(() => setTheme("light"))}>

View File

@@ -11,7 +11,7 @@ export function ComponentCard({
<AspectRatio ratio={1 / 1} asChild>
<div
className={cn(
"flex items-center justify-center rounded-md border border-slate-200 p-8 dark:border-slate-700",
"flex items-center justify-center rounded-md border p-8",
className
)}
{...props}

View File

@@ -3,13 +3,14 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { CopyButton, CopyWithClassNames } from "@/components/copy-button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { CopyButton, CopyWithClassNames } from "@/components/copy-button"
interface ComponentExampleProps extends React.HTMLAttributes<HTMLDivElement> {
extractClassname?: boolean
extractedClassNames?: string
align?: "center" | "start" | "end"
src?: string
}
export function ComponentExample({
@@ -18,6 +19,7 @@ export function ComponentExample({
extractClassname,
extractedClassNames,
align = "center",
src: _,
...props
}: ComponentExampleProps) {
const [Example, Code, ...Children] = React.Children.toArray(
@@ -31,7 +33,7 @@ export function ComponentExample({
const [, Button] = React.Children.toArray(
Code.props.children
) as React.ReactElement[]
return Button?.props?.value || null
return Button?.props?.value || Button?.props?.__rawString__ || null
}
}, [Code])
@@ -40,23 +42,38 @@ export function ComponentExample({
className={cn("group relative my-4 flex flex-col space-y-2", className)}
{...props}
>
<Tabs defaultValue="preview" className="mr-auto w-full">
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
<Tabs defaultValue="preview" className="relative mr-auto w-full">
<div className="flex items-center justify-between pb-3">
<TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="preview"
className="relative rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
className="relative rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Code
</TabsTrigger>
</TabsList>
{extractedClassNames ? (
<CopyWithClassNames
value={codeString}
classNames={extractedClassNames}
className="border-none"
className="absolute right-4 top-20"
/>
) : (
codeString && <CopyButton value={codeString} />
codeString && (
<CopyButton
value={codeString}
className="absolute right-4 top-20"
/>
)
)}
</div>
<TabsContent value="preview" className="p-0">
<TabsContent value="preview" className="rounded-md border">
<div
className={cn("flex min-h-[350px] justify-center p-10", {
"items-center": align === "center",
@@ -67,16 +84,16 @@ export function ComponentExample({
{Example}
</div>
</TabsContent>
<TabsContent value="code" className="border-none p-0">
<TabsContent value="code">
<div className="flex flex-col space-y-4">
<div className="w-full rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto [&_button]:hidden">
<div className="w-full rounded-md [&_button]:hidden [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
{Code}
</div>
{Children && (
<div className="rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto [&_button]:hidden">
{Children?.length ? (
<div className="rounded-md [&_button]:hidden [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
{Children}
</div>
)}
) : null}
</div>
</TabsContent>
</Tabs>

View File

@@ -12,7 +12,7 @@ interface ComponentSourceProps extends React.HTMLAttributes<HTMLDivElement> {
export function ComponentSource({ children, className }: ComponentSourceProps) {
return (
<CodeBlockWrapper
expandButtonTitle="View Primitive"
expandButtonTitle="Expand"
className={cn("my-6 overflow-hidden rounded-md", className)}
>
{children}

View File

@@ -4,31 +4,34 @@ import * as React from "react"
import { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu"
import { NpmCommands } from "types/unist"
import { Event, trackEvent } from "@/lib/events"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Icons } from "@/components/icons"
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
value: string
src?: string
event?: Event["name"]
}
async function copyToClipboardWithMeta(
value: string,
meta?: Record<string, unknown>
) {
async function copyToClipboardWithMeta(value: string, event?: Event) {
navigator.clipboard.writeText(value)
if (event) {
trackEvent(event)
}
}
export function CopyButton({
value,
className,
src,
event,
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = React.useState(false)
@@ -42,22 +45,30 @@ export function CopyButton({
return (
<button
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md border-slate-200 p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none dark:text-slate-100 dark:hover:bg-slate-800",
"relative z-20 inline-flex h-6 w-6 items-center justify-center rounded-md border bg-background text-sm font-medium transition-all hover:bg-muted focus:outline-none",
className
)}
onClick={() => {
copyToClipboardWithMeta(value, {
component: src,
})
copyToClipboardWithMeta(
value,
event
? {
name: event,
properties: {
code: value,
},
}
: undefined
)
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
<Icons.check className="h-3 w-3" />
) : (
<Icons.copy className="h-4 w-4" />
<Icons.copy className="h-3 w-3" />
)}
</button>
)
@@ -66,7 +77,7 @@ export function CopyButton({
interface CopyWithClassNamesProps extends DropdownMenuTriggerProps {
value: string
classNames: string
className: string
className?: string
}
export function CopyWithClassNames({
@@ -92,19 +103,19 @@ export function CopyWithClassNames({
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none dark:text-slate-100 dark:hover:bg-slate-800",
"relative z-20 inline-flex h-6 w-6 items-center justify-center rounded-md border bg-background text-sm font-medium transition-all hover:bg-muted focus:outline-none",
className
)}
{...props}
>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
<Icons.check className="h-3 w-3" />
) : (
<Icons.copy className="h-4 w-4" />
<Icons.copy className="h-3 w-3" />
)}
<span className="sr-only">Copy</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyToClipboard(value)}>
<Icons.react className="mr-2 h-4 w-4" />
<span>Component</span>
@@ -135,38 +146,53 @@ export function CopyNpmCommandButton({
}, 2000)
}, [hasCopied])
const copyCommand = React.useCallback((value: string) => {
copyToClipboardWithMeta(value)
setHasCopied(true)
}, [])
const copyCommand = React.useCallback(
(value: string, pm: "npm" | "pnpm" | "yarn") => {
copyToClipboardWithMeta(value, {
name: "copy_npm_command",
properties: {
command: value,
pm,
},
})
setHasCopied(true)
},
[]
)
return (
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
"relative z-20 inline-flex h-8 items-center justify-center rounded-md p-2 text-sm font-medium text-slate-900 transition-all hover:bg-slate-100 focus:outline-none",
"relative z-20 inline-flex h-6 w-6 items-center justify-center rounded-md border bg-background text-sm font-medium transition-all hover:bg-muted focus:outline-none",
className
)}
{...props}
>
{hasCopied ? (
<Icons.check className="h-4 w-4" />
<Icons.check className="h-3 w-3" />
) : (
<Icons.copy className="h-4 w-4" />
<Icons.copy className="h-3 w-3" />
)}
<span className="sr-only">Copy</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => copyCommand(commands.__npmCommand__)}>
<Icons.npm className="mr-2 h-4 w-4 fill-[#CB3837]" />
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => copyCommand(commands.__npmCommand__, "npm")}
>
<Icons.npm className="mr-2 h-4 w-4" />
<span>npm</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyCommand(commands.__yarnCommand__)}>
<Icons.yarn className="mr-2 h-4 w-4 fill-[#2C8EBB]" />
<DropdownMenuItem
onClick={() => copyCommand(commands.__yarnCommand__, "yarn")}
>
<Icons.yarn className="mr-2 h-4 w-4" />
<span>yarn</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyCommand(commands.__pnpmCommand__)}>
<Icons.pnpm className="mr-2 h-4 w-4 fill-[#F69220]" />
<DropdownMenuItem
onClick={() => copyCommand(commands.__pnpmCommand__, "pnpm")}
>
<Icons.pnpm className="mr-2 h-4 w-4" />
<span>pnpm</span>
</DropdownMenuItem>
</DropdownMenuContent>

View File

@@ -0,0 +1,68 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
const examples = [
{
name: "Dashboard",
href: "/examples/dashboard",
},
{
name: "Cards",
href: "/examples/cards",
},
{
name: "Tasks",
href: "/examples/tasks",
label: "New",
},
{
name: "Playground",
href: "/examples/playground",
},
{
name: "Music",
href: "/examples/music",
},
{
name: "Authentication",
href: "/examples/authentication",
},
]
interface ExamplesNavProps extends React.HTMLAttributes<HTMLDivElement> {}
export function ExamplesNav({ className, ...props }: ExamplesNavProps) {
const pathname = usePathname()
return (
<ScrollArea>
<div className={cn("mb-4 flex items-center", className)} {...props}>
{examples.map((example) => (
<Link
href={example.href}
key={example.href}
className={cn(
"flex items-center px-4",
pathname === example.href
? "font-bold text-primary"
: "font-medium text-muted-foreground"
)}
>
{example.name}{" "}
{example.label && (
<span className="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs font-medium leading-none text-[#000000] no-underline group-hover:no-underline">
{example.label}
</span>
)}
</Link>
))}
</div>
<ScrollBar orientation="horizontal" className="invisible" />
</ScrollArea>
)
}

View File

@@ -7,7 +7,7 @@ import {
export function AccordionDemo() {
return (
<Accordion type="single" collapsible className="w-[450px]">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>

View File

@@ -1,5 +1,3 @@
"use client"
import {
AlertDialog,
AlertDialogAction,
@@ -17,7 +15,7 @@ export function AlertDialogDemo() {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Open</Button>
<Button variant="outline">Show Dialog</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>

View File

@@ -0,0 +1,15 @@
import { Terminal, Waves } from "lucide-react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
export function AlertDemo() {
return (
<Alert>
<Terminal className="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
You can add components to your app using the cli.
</AlertDescription>
</Alert>
)
}

View File

@@ -0,0 +1,15 @@
import { AlertCircle, FileWarning, Terminal } from "lucide-react"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
export function AlertDestructive() {
return (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
)
}

View File

@@ -1,15 +1,13 @@
"use client"
import Image from "next/image"
import { AspectRatio } from "@/components/ui/aspect-ratio"
export function AspectRatioDemo() {
return (
<AspectRatio ratio={16 / 9} className="bg-slate-50 dark:bg-slate-800">
<AspectRatio ratio={16 / 9} className="bg-muted">
<Image
src="https://images.unsplash.com/photo-1576075796033-848c2a5f3696?w=800&dpr=2&q=80"
alt="Photo by Alvaro Pinot"
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
fill
className="rounded-md object-cover"
/>

View File

@@ -1,5 +1,3 @@
"use client"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
export function AvatarDemo() {

View File

@@ -0,0 +1,5 @@
import { Badge } from "@/components/ui/badge"
export function BadgeDemo() {
return <Badge>Badge</Badge>
}

View File

@@ -0,0 +1,5 @@
import { Badge } from "@/components/ui/badge"
export function BadgeDestructive() {
return <Badge variant="destructive">Destructive</Badge>
}

View File

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

View File

@@ -0,0 +1,5 @@
import { Badge } from "@/components/ui/badge"
export function BadgeSecondary() {
return <Badge variant="secondary">Secondary</Badge>
}

View File

@@ -0,0 +1,11 @@
import Link from "next/link"
import { Button } from "@/components/ui/button"
export function ButtonAsChild() {
return (
<Button asChild>
<Link href="/login">Login</Link>
</Button>
)
}

View File

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

View File

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

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