mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-12 10:21:39 +00:00
Compare commits
1 Commits
shadcn@2.9
...
shadcn/cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5b964460f |
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm test:*)",
|
||||
"Bash(npm run typecheck:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
20
.github/workflows/issue-stale.yml
vendored
20
.github/workflows/issue-stale.yml
vendored
@@ -18,15 +18,15 @@ jobs:
|
||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||
ascending: true
|
||||
days-before-issue-close: 7
|
||||
days-before-issue-stale: 365
|
||||
days-before-issue-stale: 365 # ~2 years
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
remove-issue-stale-when-updated: true
|
||||
stale-issue-label: "stale?"
|
||||
exempt-issue-labels: "roadmap,next"
|
||||
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you. (This is an automated message)"
|
||||
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding! (This is an automated message)"
|
||||
operations-per-run: 300
|
||||
exempt-issue-labels: "roadmap,next,bug"
|
||||
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
- uses: actions/stale@v9
|
||||
id: pr-state
|
||||
name: "Mark stale PRs, close stale PRs"
|
||||
@@ -36,10 +36,10 @@ jobs:
|
||||
days-before-issue-close: -1
|
||||
days-before-issue-stale: -1
|
||||
days-before-pr-close: 7
|
||||
days-before-pr-stale: 365
|
||||
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
|
||||
remove-pr-stale-when-updated: true
|
||||
exempt-pr-labels: "roadmap,next,bug"
|
||||
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
|
||||
stale-pr-label: "stale?"
|
||||
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you. (This is an automated message)"
|
||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding! (This is an automated message)"
|
||||
operations-per-run: 300
|
||||
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
|
||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
|
||||
@@ -98,7 +98,7 @@ To run the CLI locally, you can follow the workflow:
|
||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||
|
||||
```bash
|
||||
pnpm v4:dev
|
||||
pnpm www:dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
NEXT_PUBLIC_V0_URL=https://v0.dev
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:4000
|
||||
7
apps/v4/.gitignore
vendored
7
apps/v4/.gitignore
vendored
@@ -32,7 +32,6 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -40,9 +39,3 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
|
||||
# generated content
|
||||
.contentlayer
|
||||
.content-collections
|
||||
.source
|
||||
|
||||
@@ -4,4 +4,3 @@ node_modules
|
||||
build
|
||||
.contentlayer
|
||||
registry/__index__.tsx
|
||||
content/docs/components/calendar.mdx
|
||||
|
||||
1
apps/v4/__registry__/.autogenerated
Normal file
1
apps/v4/__registry__/.autogenerated
Normal file
@@ -0,0 +1 @@
|
||||
// The content of this directory is autogenerated by the registry server.
|
||||
1
apps/v4/__registry__/README.md
Normal file
1
apps/v4/__registry__/README.md
Normal file
@@ -0,0 +1 @@
|
||||
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
||||
3924
apps/v4/__registry__/index.tsx
Normal file
3924
apps/v4/__registry__/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,96 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { CardsDemo } from "@/components/cards"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { PageNav } from "@/components/page-nav"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Build your Component Library"
|
||||
const description =
|
||||
"A set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code."
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function IndexPage() {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs/installation">Get Started</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/blocks">Browse Blocks</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav className="hidden md:flex">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1 pb-6">
|
||||
<div className="container overflow-hidden">
|
||||
<section className="border-border/50 -mx-4 w-[160vw] overflow-hidden rounded-lg border md:hidden md:w-[150vw]">
|
||||
<Image
|
||||
src="/r/styles/new-york-v4/dashboard-01-light.png"
|
||||
width={1400}
|
||||
height={875}
|
||||
alt="Dashboard"
|
||||
className="block dark:hidden"
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/r/styles/new-york-v4/dashboard-01-dark.png"
|
||||
width={1400}
|
||||
height={875}
|
||||
alt="Dashboard"
|
||||
className="hidden dark:block"
|
||||
priority
|
||||
/>
|
||||
</section>
|
||||
<section className="theme-container hidden md:block">
|
||||
<CardsDemo />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
import { registryCategories } from "@/registry/registry-categories"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return registryCategories.map((category) => ({
|
||||
categories: [category.slug],
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function BlocksPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ categories?: string[] }>
|
||||
}) {
|
||||
const { categories = [] } = await params
|
||||
const blocks = await getAllBlockIds(["registry:block"], categories)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-12 md:gap-24">
|
||||
{blocks.map((name) => (
|
||||
<BlockDisplay name={name} key={name} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { BlocksNav } from "@/components/blocks-nav"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { PageNav } from "@/components/page-nav"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Building Blocks for the Web"
|
||||
const description =
|
||||
"Clean, modern building blocks. Copy and paste into your apps. Works with all React frameworks. Open Source. Free forever."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function BlocksLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<a href="#blocks">Browse Blocks</a>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/docs/blocks">Add a block</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav id="blocks">
|
||||
<BlocksNav />
|
||||
<Button
|
||||
asChild
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="mr-7 hidden shadow-none lg:flex"
|
||||
>
|
||||
<Link href="/blocks/sidebar">Browse all blocks</Link>
|
||||
</Button>
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1 md:py-12">
|
||||
<div className="container">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
const FEATURED_BLOCKS = [
|
||||
"dashboard-01",
|
||||
"sidebar-07",
|
||||
"sidebar-03",
|
||||
"login-03",
|
||||
"login-04",
|
||||
]
|
||||
|
||||
export default async function BlocksPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-12 md:gap-24">
|
||||
{FEATURED_BLOCKS.map((name) => (
|
||||
<BlockDisplay name={name} key={name} />
|
||||
))}
|
||||
<div className="container-wrapper">
|
||||
<div className="container flex justify-center py-6">
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/blocks/sidebar">Browse more blocks</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ChartDisplay } from "@/components/chart-display"
|
||||
import { charts } from "@/app/(app)/charts/charts"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
export const dynamicParams = false
|
||||
|
||||
interface ChartPageProps {
|
||||
params: Promise<{
|
||||
type: string
|
||||
}>
|
||||
}
|
||||
|
||||
const chartTypes = [
|
||||
"area",
|
||||
"bar",
|
||||
"line",
|
||||
"pie",
|
||||
"radar",
|
||||
"radial",
|
||||
"tooltip",
|
||||
] as const
|
||||
type ChartType = (typeof chartTypes)[number]
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return chartTypes.map((type) => ({
|
||||
type,
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function ChartPage({ params }: ChartPageProps) {
|
||||
const { type } = await params
|
||||
|
||||
if (!chartTypes.includes(type as ChartType)) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const chartType = type as ChartType
|
||||
const chartList = charts[chartType]
|
||||
|
||||
return (
|
||||
<div className="grid flex-1 gap-12 lg:gap-24">
|
||||
<h2 className="sr-only">
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)} Charts
|
||||
</h2>
|
||||
<div className="grid flex-1 scroll-mt-20 items-stretch gap-10 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:gap-10">
|
||||
{Array.from({ length: 12 }).map((_, index) => {
|
||||
const chart = chartList[index]
|
||||
return chart ? (
|
||||
<ChartDisplay
|
||||
key={chart.id}
|
||||
name={chart.id}
|
||||
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
||||
>
|
||||
<chart.component />
|
||||
</ChartDisplay>
|
||||
) : (
|
||||
<div
|
||||
key={`empty-${index}`}
|
||||
className="hidden aspect-square w-full rounded-lg border border-dashed xl:block"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,275 +1,76 @@
|
||||
import * as React from "react"
|
||||
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
||||
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
||||
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
||||
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
||||
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
||||
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
||||
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
||||
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
||||
|
||||
import { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||
import { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||
import { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
||||
import { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
||||
import { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
||||
import { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
||||
import { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
||||
import { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
||||
import { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
||||
import { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
||||
import { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
||||
import { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
||||
import { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
||||
import { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
||||
import { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
||||
import { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
||||
import { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||
import { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
||||
import { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
||||
import { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
||||
import { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
||||
import { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
||||
import { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
||||
import { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
||||
import { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
||||
import { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
||||
import { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
||||
import { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
||||
import { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
||||
import { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
||||
import { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
||||
import { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
||||
import { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
||||
import { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
||||
import { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
||||
import { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
||||
import { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
||||
import { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
||||
import { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
||||
import { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
||||
import { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
||||
import { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
||||
import { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
||||
import { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
||||
import { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
||||
import { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
||||
import { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
||||
import { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
||||
import { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
||||
import { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
||||
import { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
||||
import { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
||||
import { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
||||
import { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
||||
import { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
||||
import { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
||||
import { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
||||
import { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
||||
import { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
||||
import { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
||||
import { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
||||
import { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
||||
import { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
||||
import { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
||||
import { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
||||
import { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
||||
import { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
||||
import { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
||||
import { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
||||
import { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
||||
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
||||
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
||||
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
||||
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
||||
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
||||
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
||||
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
||||
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
||||
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
||||
|
||||
type ChartComponent = React.ComponentType
|
||||
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
||||
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
||||
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
||||
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
||||
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
||||
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
||||
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
||||
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
||||
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
||||
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
||||
|
||||
interface ChartItem {
|
||||
id: string
|
||||
component: ChartComponent
|
||||
fullWidth?: boolean
|
||||
}
|
||||
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
||||
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
||||
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
||||
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
||||
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
||||
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
||||
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
||||
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
||||
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
||||
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
||||
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
||||
|
||||
interface ChartGroups {
|
||||
area: ChartItem[]
|
||||
bar: ChartItem[]
|
||||
line: ChartItem[]
|
||||
pie: ChartItem[]
|
||||
radar: ChartItem[]
|
||||
radial: ChartItem[]
|
||||
tooltip: ChartItem[]
|
||||
}
|
||||
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
||||
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
||||
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
||||
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
||||
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
||||
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
||||
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
||||
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
||||
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
||||
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
||||
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
||||
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
||||
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
||||
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
||||
|
||||
export const charts: ChartGroups = {
|
||||
area: [
|
||||
{
|
||||
id: "chart-area-interactive",
|
||||
component: ChartAreaInteractive,
|
||||
fullWidth: true,
|
||||
},
|
||||
{ id: "chart-area-default", component: ChartAreaDefault },
|
||||
{ id: "chart-area-linear", component: ChartAreaLinear },
|
||||
{ id: "chart-area-step", component: ChartAreaStep },
|
||||
{ id: "chart-area-legend", component: ChartAreaLegend },
|
||||
{ id: "chart-area-stacked", component: ChartAreaStacked },
|
||||
{ id: "chart-area-stacked-expand", component: ChartAreaStackedExpand },
|
||||
{ id: "chart-area-icons", component: ChartAreaIcons },
|
||||
{ id: "chart-area-gradient", component: ChartAreaGradient },
|
||||
{ id: "chart-area-axes", component: ChartAreaAxes },
|
||||
],
|
||||
bar: [
|
||||
{
|
||||
id: "chart-bar-interactive",
|
||||
component: ChartBarInteractive,
|
||||
fullWidth: true,
|
||||
},
|
||||
{ id: "chart-bar-default", component: ChartBarDefault },
|
||||
{ id: "chart-bar-horizontal", component: ChartBarHorizontal },
|
||||
{ id: "chart-bar-multiple", component: ChartBarMultiple },
|
||||
{ id: "chart-bar-stacked", component: ChartBarStacked },
|
||||
{ id: "chart-bar-label", component: ChartBarLabel },
|
||||
{ id: "chart-bar-label-custom", component: ChartBarLabelCustom },
|
||||
{ id: "chart-bar-mixed", component: ChartBarMixed },
|
||||
{ id: "chart-bar-active", component: ChartBarActive },
|
||||
{ id: "chart-bar-negative", component: ChartBarNegative },
|
||||
],
|
||||
line: [
|
||||
{
|
||||
id: "chart-line-interactive",
|
||||
component: ChartLineInteractive,
|
||||
fullWidth: true,
|
||||
},
|
||||
{ id: "chart-line-default", component: ChartLineDefault },
|
||||
{ id: "chart-line-linear", component: ChartLineLinear },
|
||||
{ id: "chart-line-step", component: ChartLineStep },
|
||||
{ id: "chart-line-multiple", component: ChartLineMultiple },
|
||||
{ id: "chart-line-dots", component: ChartLineDots },
|
||||
{ id: "chart-line-dots-custom", component: ChartLineDotsCustom },
|
||||
{ id: "chart-line-dots-colors", component: ChartLineDotsColors },
|
||||
{ id: "chart-line-label", component: ChartLineLabel },
|
||||
{ id: "chart-line-label-custom", component: ChartLineLabelCustom },
|
||||
],
|
||||
pie: [
|
||||
{ id: "chart-pie-simple", component: ChartPieSimple },
|
||||
{ id: "chart-pie-separator-none", component: ChartPieSeparatorNone },
|
||||
{ id: "chart-pie-label", component: ChartPieLabel },
|
||||
{ id: "chart-pie-label-custom", component: ChartPieLabelCustom },
|
||||
{ id: "chart-pie-label-list", component: ChartPieLabelList },
|
||||
{ id: "chart-pie-legend", component: ChartPieLegend },
|
||||
{ id: "chart-pie-donut", component: ChartPieDonut },
|
||||
{ id: "chart-pie-donut-active", component: ChartPieDonutActive },
|
||||
{ id: "chart-pie-donut-text", component: ChartPieDonutText },
|
||||
{ id: "chart-pie-stacked", component: ChartPieStacked },
|
||||
{ id: "chart-pie-interactive", component: ChartPieInteractive },
|
||||
],
|
||||
radar: [
|
||||
{ id: "chart-radar-default", component: ChartRadarDefault },
|
||||
{ id: "chart-radar-dots", component: ChartRadarDots },
|
||||
{ id: "chart-radar-lines-only", component: ChartRadarLinesOnly },
|
||||
{ id: "chart-radar-label-custom", component: ChartRadarLabelCustom },
|
||||
{ id: "chart-radar-grid-custom", component: ChartRadarGridCustom },
|
||||
{ id: "chart-radar-grid-none", component: ChartRadarGridNone },
|
||||
{ id: "chart-radar-grid-circle", component: ChartRadarGridCircle },
|
||||
{
|
||||
id: "chart-radar-grid-circle-no-lines",
|
||||
component: ChartRadarGridCircleNoLines,
|
||||
},
|
||||
{ id: "chart-radar-grid-circle-fill", component: ChartRadarGridCircleFill },
|
||||
{ id: "chart-radar-grid-fill", component: ChartRadarGridFill },
|
||||
{ id: "chart-radar-multiple", component: ChartRadarMultiple },
|
||||
{ id: "chart-radar-legend", component: ChartRadarLegend },
|
||||
{ id: "chart-radar-icons", component: ChartRadarIcons },
|
||||
{ id: "chart-radar-radius", component: ChartRadarRadius },
|
||||
],
|
||||
radial: [
|
||||
{ id: "chart-radial-simple", component: ChartRadialSimple },
|
||||
{ id: "chart-radial-label", component: ChartRadialLabel },
|
||||
{ id: "chart-radial-grid", component: ChartRadialGrid },
|
||||
{ id: "chart-radial-text", component: ChartRadialText },
|
||||
{ id: "chart-radial-shape", component: ChartRadialShape },
|
||||
{ id: "chart-radial-stacked", component: ChartRadialStacked },
|
||||
],
|
||||
tooltip: [
|
||||
{ id: "chart-tooltip-default", component: ChartTooltipDefault },
|
||||
{
|
||||
id: "chart-tooltip-indicator-line",
|
||||
component: ChartTooltipIndicatorLine,
|
||||
},
|
||||
{
|
||||
id: "chart-tooltip-indicator-none",
|
||||
component: ChartTooltipIndicatorNone,
|
||||
},
|
||||
{ id: "chart-tooltip-label-custom", component: ChartTooltipLabelCustom },
|
||||
{
|
||||
id: "chart-tooltip-label-formatter",
|
||||
component: ChartTooltipLabelFormatter,
|
||||
},
|
||||
{ id: "chart-tooltip-label-none", component: ChartTooltipLabelNone },
|
||||
{ id: "chart-tooltip-formatter", component: ChartTooltipFormatter },
|
||||
{ id: "chart-tooltip-icons", component: ChartTooltipIcons },
|
||||
{ id: "chart-tooltip-advanced", component: ChartTooltipAdvanced },
|
||||
],
|
||||
}
|
||||
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
||||
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
||||
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
||||
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
||||
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
||||
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
||||
|
||||
// Export individual components for backward compatibility
|
||||
export {
|
||||
ChartAreaDefault,
|
||||
ChartAreaLinear,
|
||||
ChartAreaStep,
|
||||
ChartAreaLegend,
|
||||
ChartAreaStacked,
|
||||
ChartAreaStackedExpand,
|
||||
ChartAreaIcons,
|
||||
ChartAreaGradient,
|
||||
ChartAreaAxes,
|
||||
ChartAreaInteractive,
|
||||
ChartBarDefault,
|
||||
ChartBarHorizontal,
|
||||
ChartBarMultiple,
|
||||
ChartBarStacked,
|
||||
ChartBarLabel,
|
||||
ChartBarLabelCustom,
|
||||
ChartBarMixed,
|
||||
ChartBarActive,
|
||||
ChartBarNegative,
|
||||
ChartBarInteractive,
|
||||
ChartLineDefault,
|
||||
ChartLineLinear,
|
||||
ChartLineStep,
|
||||
ChartLineMultiple,
|
||||
ChartLineDots,
|
||||
ChartLineDotsCustom,
|
||||
ChartLineDotsColors,
|
||||
ChartLineLabel,
|
||||
ChartLineLabelCustom,
|
||||
ChartLineInteractive,
|
||||
ChartPieSimple,
|
||||
ChartPieSeparatorNone,
|
||||
ChartPieLabel,
|
||||
ChartPieLabelCustom,
|
||||
ChartPieLabelList,
|
||||
ChartPieLegend,
|
||||
ChartPieDonut,
|
||||
ChartPieDonutActive,
|
||||
ChartPieDonutText,
|
||||
ChartPieStacked,
|
||||
ChartPieInteractive,
|
||||
ChartRadarDefault,
|
||||
ChartRadarDots,
|
||||
ChartRadarLinesOnly,
|
||||
ChartRadarLabelCustom,
|
||||
ChartRadarGridCustom,
|
||||
ChartRadarGridNone,
|
||||
ChartRadarGridCircle,
|
||||
ChartRadarGridCircleNoLines,
|
||||
ChartRadarGridCircleFill,
|
||||
ChartRadarGridFill,
|
||||
ChartRadarMultiple,
|
||||
ChartRadarLegend,
|
||||
ChartRadarIcons,
|
||||
ChartRadarRadius,
|
||||
ChartRadialSimple,
|
||||
ChartRadialLabel,
|
||||
ChartRadialGrid,
|
||||
ChartRadialText,
|
||||
ChartRadialShape,
|
||||
ChartRadialStacked,
|
||||
ChartTooltipDefault,
|
||||
ChartTooltipIndicatorLine,
|
||||
ChartTooltipIndicatorNone,
|
||||
ChartTooltipLabelCustom,
|
||||
ChartTooltipLabelFormatter,
|
||||
ChartTooltipLabelNone,
|
||||
ChartTooltipFormatter,
|
||||
ChartTooltipIcons,
|
||||
ChartTooltipAdvanced,
|
||||
}
|
||||
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
||||
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
||||
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
||||
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
||||
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
||||
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
||||
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
||||
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
||||
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ChartsNav } from "@/components/charts-nav"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { PageNav } from "@/components/page-nav"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Beautiful Charts & Graphs"
|
||||
const description =
|
||||
"A collection of ready-to-use chart components built with Recharts. From basic charts to rich data displays, copy and paste into your apps."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function ChartsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<a href="#charts">Browse Charts</a>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/docs/components/chart">Documentation</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav id="charts">
|
||||
<ChartsNav />
|
||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex-1">
|
||||
<div className="container pb-6">
|
||||
<section className="theme-container">{children}</section>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
20
apps/v4/app/(app)/charts/page.tsx
Normal file
20
apps/v4/app/(app)/charts/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import * as Charts from "@/app/(app)/charts/charts"
|
||||
|
||||
export default function ChartsPage() {
|
||||
return (
|
||||
<div className="grid flex-1 grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
|
||||
{Object.entries(Charts)
|
||||
.sort()
|
||||
.map(([key, Component]) => (
|
||||
<ComponentWrapper
|
||||
key={key}
|
||||
name={key}
|
||||
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
|
||||
>
|
||||
<Component />
|
||||
</ComponentWrapper>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ColorsNav } from "@/components/colors-nav"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Tailwind Colors in Every Format"
|
||||
const description =
|
||||
"The complete Tailwind color palette in HEX, RGB, HSL, CSS variables, and classes. Ready to copy and paste into your project."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function ColorsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<a href="#colors">Browse Colors</a>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/docs/theming">Documentation</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<div className="hidden">
|
||||
<div className="container-wrapper">
|
||||
<div className="container flex items-center justify-between gap-8 py-4">
|
||||
<ColorsNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-wrapper">
|
||||
<div className="container py-6">
|
||||
<section id="colors" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { getColors } from "@/lib/colors"
|
||||
import { ColorPalette } from "@/components/color-palette"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
export default function ColorsPage() {
|
||||
const colors = getColors()
|
||||
|
||||
return (
|
||||
<div className="grid gap-8 lg:gap-16 xl:gap-20">
|
||||
{colors.map((colorPalette) => (
|
||||
<ColorPalette key={colorPalette.name} colorPalette={colorPalette} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import { notFound } from "next/navigation"
|
||||
import { mdxComponents } from "@/mdx-components"
|
||||
import {
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconArrowUpRight,
|
||||
} from "@tabler/icons-react"
|
||||
import { findNeighbour } from "fumadocs-core/server"
|
||||
|
||||
import { source } from "@/lib/source"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
import { DocsCopyPage } from "@/components/docs-copy-page"
|
||||
import { DocsTableOfContents } from "@/components/docs-toc"
|
||||
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
export const dynamicParams = false
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const doc = page.data
|
||||
|
||||
if (!doc.title || !doc.description) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return {
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
openGraph: {
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
type: "article",
|
||||
url: absoluteUrl(page.url),
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
doc.title
|
||||
)}&description=${encodeURIComponent(doc.description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: doc.title,
|
||||
description: doc.description,
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
doc.title
|
||||
)}&description=${encodeURIComponent(doc.description)}`,
|
||||
},
|
||||
],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const doc = page.data
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const MDX = doc.body
|
||||
const neighbours = await findNeighbour(source.pageTree, page.url)
|
||||
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
const links = doc.links
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="docs"
|
||||
className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<h1 className="scroll-m-20 text-4xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">
|
||||
{doc.title}
|
||||
</h1>
|
||||
<div className="docs-nav bg-background/80 border-border/50 fixed inset-x-0 bottom-0 isolate z-50 flex items-center gap-2 border-t px-6 py-4 backdrop-blur-sm sm:static sm:z-0 sm:border-t-0 sm:bg-transparent sm:px-0 sm:pt-1.5 sm:backdrop-blur-none">
|
||||
<DocsCopyPage
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
page={doc.content}
|
||||
url={absoluteUrl(page.url)}
|
||||
/>
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target ml-auto size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft />
|
||||
<span className="sr-only">Previous</span>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="extend-touch-target size-8 shadow-none md:size-7"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
<span className="sr-only">Next</span>
|
||||
<IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{doc.description && (
|
||||
<p className="text-muted-foreground text-[1.05rem] text-balance sm:text-base">
|
||||
{doc.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{links ? (
|
||||
<div className="flex items-center space-x-2 pt-4">
|
||||
{links?.doc && (
|
||||
<Badge asChild variant="secondary">
|
||||
<Link href={links.doc} target="_blank" rel="noreferrer">
|
||||
Docs <IconArrowUpRight />
|
||||
</Link>
|
||||
</Badge>
|
||||
)}
|
||||
{links?.api && (
|
||||
<Badge asChild variant="secondary">
|
||||
<Link href={links.api} target="_blank" rel="noreferrer">
|
||||
API Reference <IconArrowUpRight />
|
||||
</Link>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
|
||||
<MDX components={mdxComponents} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto hidden h-16 w-full max-w-2xl items-center gap-2 px-4 sm:flex md:px-0">
|
||||
{neighbours.previous && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
asChild
|
||||
className="shadow-none"
|
||||
>
|
||||
<Link href={neighbours.previous.url}>
|
||||
<IconArrowLeft /> {neighbours.previous.name}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{neighbours.next && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="ml-auto shadow-none"
|
||||
asChild
|
||||
>
|
||||
<Link href={neighbours.next.url}>
|
||||
{neighbours.next.name} <IconArrowRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--header-height)-var(--footer-height))] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
{doc.toc?.length ? (
|
||||
<div className="no-scrollbar overflow-y-auto px-8">
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
<DocsTableOfContents toc={doc.toc} />
|
||||
<div className="h-12" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-1 flex-col gap-12 px-6">
|
||||
<OpenInV0Cta />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { source } from "@/lib/source"
|
||||
import { DocsSidebar } from "@/components/docs-sidebar"
|
||||
import { SidebarProvider } from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container-wrapper flex flex-1 flex-col px-2">
|
||||
<SidebarProvider className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--sidebar-width:220px] [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--sidebar-width:240px] lg:[--top-spacing:calc(var(--spacing)*4)]">
|
||||
<DocsSidebar tree={source.pageTree} />
|
||||
<div className="h-full w-full">{children}</div>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function UserAuthForm({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
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 text-muted-foreground px-2">
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/new-york-v4/ui/button"
|
||||
import { UserAuthForm } from "@/app/(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"
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/examples/authentication-dark.png"
|
||||
width={1280}
|
||||
height={843}
|
||||
alt="Authentication"
|
||||
className="hidden dark:block"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="relative container hidden flex-1 shrink-0 items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||
<Link
|
||||
href="/examples/authentication"
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"absolute top-4 right-4 md:top-8 md:right-8"
|
||||
)}
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<div className="text-primary relative hidden h-full flex-col p-10 lg:flex dark:border-r">
|
||||
<div className="bg-primary/5 absolute inset-0" />
|
||||
<div className="relative z-20 flex items-center text-lg font-medium">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-6 w-6"
|
||||
>
|
||||
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
|
||||
</svg>
|
||||
Acme Inc
|
||||
</div>
|
||||
<div className="relative z-20 mt-auto">
|
||||
<blockquote className="leading-normal text-balance">
|
||||
“This library has saved me countless hours of work and
|
||||
helped me deliver stunning designs to my clients faster than ever
|
||||
before.” - Sofia Davis
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center lg:h-[1000px] lg:p-8">
|
||||
<div className="mx-auto flex w-full flex-col justify-center gap-6 sm:w-[350px]">
|
||||
<div className="flex flex-col gap-2 text-center">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Create an account
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Enter your email below to create your account
|
||||
</p>
|
||||
</div>
|
||||
<UserAuthForm />
|
||||
<p className="text-muted-foreground px-8 text-center text-sm">
|
||||
By clicking continue, you agree to our{" "}
|
||||
<Link
|
||||
href="/terms"
|
||||
className="hover:text-primary underline underline-offset-4"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="hover:text-primary underline underline-offset-4"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
IconCamera,
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFileWord,
|
||||
IconFolder,
|
||||
IconHelp,
|
||||
IconInnerShadowTop,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavDocuments } from "@/app/(app)/examples/dashboard/components/nav-documents"
|
||||
import { NavMain } from "@/app/(app)/examples/dashboard/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(app)/examples/dashboard/components/nav-secondary"
|
||||
import { NavUser } from "@/app/(app)/examples/dashboard/components/nav-user"
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: IconListDetails,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "#",
|
||||
icon: IconChartBar,
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: IconFolder,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: IconUsers,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: IconCamera,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Proposal",
|
||||
icon: IconFileDescription,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Prompts",
|
||||
icon: IconFileAi,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: IconHelp,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: IconSearch,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: IconDatabase,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: IconReport,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: IconFileWord,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="none" className="h-auto border-r" {...props}>
|
||||
<SidebarHeader className="border-b">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
>
|
||||
<Link href="#">
|
||||
<IconInnerShadowTop className="!size-5" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||
|
||||
export const description = "An interactive area chart"
|
||||
|
||||
const chartData = [
|
||||
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
},
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function ChartAreaInteractive() {
|
||||
const isMobile = useIsMobile()
|
||||
const [timeRange, setTimeRange] = React.useState("90d")
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) {
|
||||
setTimeRange("7d")
|
||||
}
|
||||
}, [isMobile])
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const referenceDate = new Date("2024-06-30")
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7
|
||||
}
|
||||
const startDate = new Date(referenceDate)
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||
return date >= startDate
|
||||
})
|
||||
|
||||
return (
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardTitle>Total Visitors</CardTitle>
|
||||
<CardDescription>
|
||||
<span className="hidden @[540px]/card:block">
|
||||
Total for the last 3 months
|
||||
</span>
|
||||
<span className="@[540px]/card:hidden">Last 3 months</span>
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={timeRange}
|
||||
onValueChange={setTimeRange}
|
||||
variant="outline"
|
||||
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||
>
|
||||
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
||||
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
||||
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger
|
||||
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||
size="sm"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<SelectValue placeholder="Last 3 months" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-xl">
|
||||
<SelectItem value="90d" className="rounded-lg">
|
||||
Last 3 months
|
||||
</SelectItem>
|
||||
<SelectItem value="30d" className="rounded-lg">
|
||||
Last 30 days
|
||||
</SelectItem>
|
||||
<SelectItem value="7d" className="rounded-lg">
|
||||
Last 7 days
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="aspect-auto h-[250px] w-full"
|
||||
>
|
||||
<AreaChart data={filteredData}>
|
||||
<defs>
|
||||
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-desktop)"
|
||||
stopOpacity={1.0}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-desktop)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-mobile)"
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-mobile)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={32}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
defaultIndex={isMobile ? -1 : 10}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
}}
|
||||
indicator="dot"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="url(#fillMobile)"
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="url(#fillDesktop)"
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,807 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
type UniqueIdentifier,
|
||||
} from "@dnd-kit/core"
|
||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable"
|
||||
import { CSS } from "@dnd-kit/utilities"
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconChevronsLeft,
|
||||
IconChevronsRight,
|
||||
IconCircleCheckFilled,
|
||||
IconDotsVertical,
|
||||
IconGripVertical,
|
||||
IconLayoutColumns,
|
||||
IconLoader,
|
||||
IconPlus,
|
||||
IconTrendingUp,
|
||||
} from "@tabler/icons-react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
Row,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
VisibilityState,
|
||||
} from "@tanstack/react-table"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/registry/new-york-v4/ui/chart"
|
||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/new-york-v4/ui/drawer"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york-v4/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
|
||||
export const schema = z.object({
|
||||
id: z.number(),
|
||||
header: z.string(),
|
||||
type: z.string(),
|
||||
status: z.string(),
|
||||
target: z.string(),
|
||||
limit: z.string(),
|
||||
reviewer: z.string(),
|
||||
})
|
||||
|
||||
// Create a separate component for the drag handle
|
||||
function DragHandle({ id }: { id: number }) {
|
||||
const { attributes, listeners } = useSortable({
|
||||
id,
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground size-7 hover:bg-transparent"
|
||||
>
|
||||
<IconGripVertical className="text-muted-foreground size-3" />
|
||||
<span className="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
{
|
||||
id: "drag",
|
||||
header: () => null,
|
||||
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "header",
|
||||
header: "Header",
|
||||
cell: ({ row }) => {
|
||||
return <TableCellViewer item={row.original} />
|
||||
},
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "Section Type",
|
||||
cell: ({ row }) => (
|
||||
<div className="w-32">
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
{row.original.type}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
{row.original.status === "Done" ? (
|
||||
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||
) : (
|
||||
<IconLoader />
|
||||
)}
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "target",
|
||||
header: () => <div className="w-full text-right">Target</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||
Target
|
||||
</Label>
|
||||
<Input
|
||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||
defaultValue={row.original.target}
|
||||
id={`${row.original.id}-target`}
|
||||
/>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "limit",
|
||||
header: () => <div className="w-full text-right">Limit</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||
Limit
|
||||
</Label>
|
||||
<Input
|
||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||
defaultValue={row.original.limit}
|
||||
id={`${row.original.id}-limit`}
|
||||
/>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "reviewer",
|
||||
header: "Reviewer",
|
||||
cell: ({ row }) => {
|
||||
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||
|
||||
if (isAssigned) {
|
||||
return row.original.reviewer
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
||||
Reviewer
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger
|
||||
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||
size="sm"
|
||||
id={`${row.original.id}-reviewer`}
|
||||
>
|
||||
<SelectValue placeholder="Assign reviewer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">
|
||||
Jamik Tashpulatov
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||
size="icon"
|
||||
>
|
||||
<IconDotsVertical />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-32">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||
id: row.original.id,
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
data-dragging={isDragging}
|
||||
ref={setNodeRef}
|
||||
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||
style={{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition: transition,
|
||||
}}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
export function DataTable({
|
||||
data: initialData,
|
||||
}: {
|
||||
data: z.infer<typeof schema>[]
|
||||
}) {
|
||||
const [data, setData] = React.useState(() => initialData)
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({})
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [pagination, setPagination] = React.useState({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
})
|
||||
const sortableId = React.useId()
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, {}),
|
||||
useSensor(TouchSensor, {}),
|
||||
useSensor(KeyboardSensor, {})
|
||||
)
|
||||
|
||||
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||
() => data?.map(({ id }) => id) || [],
|
||||
[data]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
columnFilters,
|
||||
pagination,
|
||||
},
|
||||
getRowId: (row) => row.id.toString(),
|
||||
enableRowSelection: true,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onPaginationChange: setPagination,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
})
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
if (active && over && active.id !== over.id) {
|
||||
setData((data) => {
|
||||
const oldIndex = dataIds.indexOf(active.id)
|
||||
const newIndex = dataIds.indexOf(over.id)
|
||||
return arrayMove(data, oldIndex, newIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="outline"
|
||||
className="w-full flex-col justify-start gap-6"
|
||||
>
|
||||
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||
<Label htmlFor="view-selector" className="sr-only">
|
||||
View
|
||||
</Label>
|
||||
<Select defaultValue="outline">
|
||||
<SelectTrigger
|
||||
className="flex w-fit @4xl/main:hidden"
|
||||
size="sm"
|
||||
id="view-selector"
|
||||
>
|
||||
<SelectValue placeholder="Select a view" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="outline">Outline</SelectItem>
|
||||
<SelectItem value="past-performance">Past Performance</SelectItem>
|
||||
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
||||
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||
<TabsTrigger value="past-performance">
|
||||
Past Performance <Badge variant="secondary">3</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="key-personnel">
|
||||
Key Personnel <Badge variant="secondary">2</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<IconLayoutColumns />
|
||||
<span className="hidden lg:inline">Customize Columns</span>
|
||||
<span className="lg:hidden">Columns</span>
|
||||
<IconChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
{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>
|
||||
<Button variant="outline" size="sm">
|
||||
<IconPlus />
|
||||
<span className="hidden lg:inline">Add Section</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent
|
||||
value="outline"
|
||||
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg border">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
id={sortableId}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader className="bg-muted sticky top-0 z-10">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
||||
{table.getRowModel().rows?.length ? (
|
||||
<SortableContext
|
||||
items={dataIds}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<DraggableRow key={row.id} row={row} />
|
||||
))}
|
||||
</SortableContext>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DndContext>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||
<div className="hidden items-center gap-2 lg:flex">
|
||||
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
||||
Rows per page
|
||||
</Label>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value))
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||
<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-fit items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||
<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>
|
||||
<IconChevronsLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="size-8"
|
||||
size="icon"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<IconChevronLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="size-8"
|
||||
size="icon"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<IconChevronRight />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden size-8 lg:flex"
|
||||
size="icon"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<IconChevronsRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="past-performance"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="focus-documents"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
return (
|
||||
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||
{item.header}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="gap-1">
|
||||
<DrawerTitle>{item.header}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Showing total visitors for the last 6 months
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||
{!isMobile && (
|
||||
<>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 0,
|
||||
right: 10,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
hide
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.6}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
<Separator />
|
||||
<div className="grid gap-2">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month{" "}
|
||||
<IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Showing total visitors for the last 6 months. This is just
|
||||
some random text to test the layout. It spans multiple lines
|
||||
and should wrap around.
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
<form className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="header">Header</Label>
|
||||
<Input id="header" defaultValue={item.header} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="type">Type</Label>
|
||||
<Select defaultValue={item.type}>
|
||||
<SelectTrigger id="type" className="w-full">
|
||||
<SelectValue placeholder="Select a type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Table of Contents">
|
||||
Table of Contents
|
||||
</SelectItem>
|
||||
<SelectItem value="Executive Summary">
|
||||
Executive Summary
|
||||
</SelectItem>
|
||||
<SelectItem value="Technical Approach">
|
||||
Technical Approach
|
||||
</SelectItem>
|
||||
<SelectItem value="Design">Design</SelectItem>
|
||||
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||
<SelectItem value="Focus Documents">
|
||||
Focus Documents
|
||||
</SelectItem>
|
||||
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="status">Status</Label>
|
||||
<Select defaultValue={item.status}>
|
||||
<SelectTrigger id="status" className="w-full">
|
||||
<SelectValue placeholder="Select a status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Done">Done</SelectItem>
|
||||
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||
<SelectItem value="Not Started">Not Started</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="target">Target</Label>
|
||||
<Input id="target" defaultValue={item.target} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="limit">Limit</Label>
|
||||
<Input id="limit" defaultValue={item.limit} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label htmlFor="reviewer">Reviewer</Label>
|
||||
<Select defaultValue={item.reviewer}>
|
||||
<SelectTrigger id="reviewer" className="w-full">
|
||||
<SelectValue placeholder="Select a reviewer" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||
<SelectItem value="Jamik Tashpulatov">
|
||||
Jamik Tashpulatov
|
||||
</SelectItem>
|
||||
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">Done</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconDots,
|
||||
IconFolder,
|
||||
IconShare3,
|
||||
IconTrash,
|
||||
type Icon,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavDocuments({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
name: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
showOnHover
|
||||
className="data-[state=open]:bg-accent rounded-sm"
|
||||
>
|
||||
<IconDots />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-24 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<IconFolder />
|
||||
<span>Open</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconShare3 />
|
||||
<span>Share</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<IconTrash />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<IconDots className="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { type Icon } from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon?: Icon
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent>
|
||||
<SidebarGroupLabel>Home</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type Icon } from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
IconNotification,
|
||||
IconUserCircle,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
<IconDotsVertical className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconUserCircle />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconNotification />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<IconLogout />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
|
||||
export function SectionCards() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Total Revenue</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
$1,250.00
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Trending up this month <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>New Customers</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
1,234
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingDown />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Down 20% this period <IconTrendingDown className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Acquisition needs attention
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Active Accounts</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
45,678
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Strong user retention <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">Engagement exceed targets</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Growth Rate</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
4.5%
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Steady performance increase <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">Meets growth projections</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { IconCirclePlusFilled } from "@tabler/icons-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="bg-background/90 sticky top-0 z-10 flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button size="sm" className="hidden h-7 sm:flex">
|
||||
<IconCirclePlusFilled />
|
||||
<span>Quick Create</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,614 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"header": "Cover page",
|
||||
"type": "Cover page",
|
||||
"status": "In Process",
|
||||
"target": "18",
|
||||
"limit": "5",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"header": "Table of contents",
|
||||
"type": "Table of contents",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "24",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"header": "Executive summary",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "10",
|
||||
"limit": "13",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"header": "Technical approach",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "27",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"header": "Design",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "2",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"header": "Capabilities",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "20",
|
||||
"limit": "8",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"header": "Integration with existing systems",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "21",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"header": "Innovation and Advantages",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "25",
|
||||
"limit": "26",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"header": "Overview of EMR's Innovative Solutions",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "7",
|
||||
"limit": "23",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"header": "Advanced Algorithms and Machine Learning",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "28",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"header": "Adaptive Communication Protocols",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "9",
|
||||
"limit": "31",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"header": "Advantages Over Current Technologies",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "0",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"header": "Past Performance",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "33",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"header": "Customer Feedback and Satisfaction Levels",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "34",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"header": "Implementation Challenges and Solutions",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "3",
|
||||
"limit": "35",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"header": "Security Measures and Data Protection Policies",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "6",
|
||||
"limit": "36",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"header": "Scalability and Future Proofing",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "4",
|
||||
"limit": "37",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"header": "Cost-Benefit Analysis",
|
||||
"type": "Plain language",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "38",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"header": "User Training and Onboarding Experience",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "17",
|
||||
"limit": "39",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"header": "Future Development Roadmap",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "11",
|
||||
"limit": "40",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"header": "System Architecture Overview",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "24",
|
||||
"limit": "18",
|
||||
"reviewer": "Maya Johnson"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"header": "Risk Management Plan",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "22",
|
||||
"reviewer": "Carlos Rodriguez"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"header": "Compliance Documentation",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "31",
|
||||
"limit": "27",
|
||||
"reviewer": "Sarah Chen"
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"header": "API Documentation",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "8",
|
||||
"limit": "12",
|
||||
"reviewer": "Raj Patel"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"header": "User Interface Mockups",
|
||||
"type": "Visual",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "25",
|
||||
"reviewer": "Leila Ahmadi"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"header": "Database Schema",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "20",
|
||||
"reviewer": "Thomas Wilson"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"header": "Testing Methodology",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "14",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"header": "Deployment Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "30",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"header": "Budget Breakdown",
|
||||
"type": "Financial",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"header": "Market Analysis",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Sophia Martinez"
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"header": "Competitor Comparison",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"header": "Maintenance Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "23",
|
||||
"reviewer": "Alex Thompson"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"header": "User Personas",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "24",
|
||||
"reviewer": "Nina Patel"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"header": "Accessibility Compliance",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"header": "Performance Metrics",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "David Kim"
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"header": "Disaster Recovery Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"header": "Third-party Integrations",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"header": "User Feedback Summary",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "15",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"header": "Localization Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "12",
|
||||
"limit": "19",
|
||||
"reviewer": "Maria Garcia"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"header": "Mobile Compatibility",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "James Wilson"
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"header": "Data Migration Plan",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"header": "Quality Assurance Protocols",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Priya Singh"
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"header": "Stakeholder Analysis",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "11",
|
||||
"limit": "14",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"header": "Environmental Impact Assessment",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"header": "Intellectual Property Rights",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "20",
|
||||
"reviewer": "Sarah Johnson"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"header": "Customer Support Framework",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"header": "Version Control Strategy",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"header": "Continuous Integration Pipeline",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Michael Chen"
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"header": "Regulatory Compliance",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"header": "User Authentication System",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"header": "Data Analytics Framework",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"header": "Cloud Infrastructure",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"header": "Network Security Measures",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Lisa Wong"
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"header": "Project Timeline",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"header": "Resource Allocation",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"header": "Team Structure and Roles",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"header": "Communication Protocols",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"header": "Success Metrics",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"header": "Internationalization Support",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"header": "Backup and Recovery Procedures",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"header": "Monitoring and Alerting System",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Daniel Park"
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"header": "Code Review Guidelines",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "15",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"header": "Documentation Standards",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"header": "Release Management Process",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"header": "Feature Prioritization Matrix",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Emma Davis"
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"header": "Technical Debt Assessment",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"header": "Capacity Planning",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"header": "Service Level Agreements",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Assign reviewer"
|
||||
}
|
||||
]
|
||||
@@ -1,63 +0,0 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(app)/examples/dashboard/components/app-sidebar"
|
||||
import { ChartAreaInteractive } from "@/app/(app)/examples/dashboard/components/chart-area-interactive"
|
||||
import { DataTable } from "@/app/(app)/examples/dashboard/components/data-table"
|
||||
import { SectionCards } from "@/app/(app)/examples/dashboard/components/section-cards"
|
||||
import { SiteHeader } from "@/app/(app)/examples/dashboard/components/site-header"
|
||||
|
||||
import data from "./data.json"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/dashboard-light.png"
|
||||
width={1280}
|
||||
height={843}
|
||||
alt="Authentication"
|
||||
className="block dark:hidden"
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/examples/dashboard-dark.png"
|
||||
width={1280}
|
||||
height={843}
|
||||
alt="Authentication"
|
||||
className="hidden dark:block"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<SidebarProvider
|
||||
className="hidden md:flex"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 64)",
|
||||
"--header-height": "calc(var(--spacing) * 12 + 1px)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="sidebar" />
|
||||
<SidebarInset>
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<SectionCards />
|
||||
<div className="px-4 lg:px-6">
|
||||
<ChartAreaInteractive />
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { PageNav } from "@/components/page-nav"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
const title = "Examples"
|
||||
const description = "Check out some examples app built using the components."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function ExamplesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Build your Component Library</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
A set of beautifully-designed, accessible components and a code
|
||||
distribution platform. Works with your favorite frameworks. Open
|
||||
Source. Open Code.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs">Get Started</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/blocks">Browse Blocks</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<PageNav id="examples">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||
<ThemeSelector className="mr-4 hidden md:block" />
|
||||
</PageNav>
|
||||
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
|
||||
<div className="theme-container container flex flex-1 scroll-mt-20 flex-col">
|
||||
<div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding md:flex-1 xl:rounded-xl">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dialog"
|
||||
|
||||
export function CodeViewer() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary">View code</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>View code</DialogTitle>
|
||||
<DialogDescription>
|
||||
You can use the following code to start integrating your current
|
||||
prompt and settings into your application.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4">
|
||||
<div className="rounded-md bg-black p-6">
|
||||
<pre>
|
||||
<code className="grid gap-1 text-sm text-white [&_span]:h-4">
|
||||
<span>
|
||||
<span className="text-sky-300">import</span> os
|
||||
</span>
|
||||
<span>
|
||||
<span className="text-sky-300">import</span> openai
|
||||
</span>
|
||||
<span />
|
||||
<span>
|
||||
openai.api_key = os.getenv(
|
||||
<span className="text-green-300">
|
||||
"OPENAI_API_KEY"
|
||||
</span>
|
||||
)
|
||||
</span>
|
||||
<span />
|
||||
<span>response = openai.Completion.create(</span>
|
||||
<span>
|
||||
{" "}
|
||||
model=
|
||||
<span className="text-green-300">"davinci"</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
prompt=<span className="text-amber-300">""</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
temperature=<span className="text-amber-300">0.9</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
max_tokens=<span className="text-amber-300">5</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
top_p=<span className="text-amber-300">1</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
frequency_penalty=<span className="text-amber-300">0</span>,
|
||||
</span>
|
||||
<span>
|
||||
{" "}
|
||||
presence_penalty=<span className="text-green-300">0</span>,
|
||||
</span>
|
||||
<span>)</span>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Your API Key can be found here. You should use environment
|
||||
variables or a secret management tool to expose your key to your
|
||||
applications.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface MaxLengthSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
}
|
||||
|
||||
export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {
|
||||
const [value, setValue] = React.useState(defaultValue)
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 pt-2">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxlength">Maximum Length</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maxlength"
|
||||
max={4000}
|
||||
defaultValue={value}
|
||||
step={10}
|
||||
onValueChange={setValue}
|
||||
aria-label="Maximum Length"
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
align="start"
|
||||
className="w-[260px] text-sm"
|
||||
side="left"
|
||||
>
|
||||
The maximum number of tokens to generate. Requests can use up to 2,048
|
||||
or 4,000 tokens, shared between prompt and completion. The exact limit
|
||||
varies by model.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
import { Model, ModelType } from "../data/models"
|
||||
|
||||
interface ModelSelectorProps extends PopoverProps {
|
||||
types: readonly ModelType[]
|
||||
models: Model[]
|
||||
}
|
||||
|
||||
export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [selectedModel, setSelectedModel] = React.useState<Model>(models[0])
|
||||
const [peekedModel, setPeekedModel] = React.useState<Model>(models[0])
|
||||
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<Label htmlFor="model">Model</Label>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
align="start"
|
||||
className="w-[260px] text-sm"
|
||||
side="left"
|
||||
>
|
||||
The model which will generate the completion. Some models are suitable
|
||||
for natural language tasks, others specialize in code. Learn more.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<Popover open={open} onOpenChange={setOpen} {...props}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
aria-label="Select a model"
|
||||
className="w-full justify-between"
|
||||
>
|
||||
{selectedModel ? selectedModel.name : "Select a model..."}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-[250px] p-0">
|
||||
<HoverCard>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
forceMount
|
||||
className="min-h-[280px]"
|
||||
>
|
||||
<div className="grid gap-2">
|
||||
<h4 className="leading-none font-medium">{peekedModel.name}</h4>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{peekedModel.description}
|
||||
</div>
|
||||
{peekedModel.strengths ? (
|
||||
<div className="mt-4 grid gap-2">
|
||||
<h5 className="text-sm leading-none font-medium">
|
||||
Strengths
|
||||
</h5>
|
||||
<ul className="text-muted-foreground text-sm">
|
||||
{peekedModel.strengths}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
<Command loop>
|
||||
<CommandList className="h-(--cmdk-list-height) max-h-[400px]">
|
||||
<CommandInput placeholder="Search Models..." />
|
||||
<CommandEmpty>No Models found.</CommandEmpty>
|
||||
<HoverCardTrigger />
|
||||
{types.map((type) => (
|
||||
<CommandGroup key={type} heading={type}>
|
||||
{models
|
||||
.filter((model) => model.type === type)
|
||||
.map((model) => (
|
||||
<ModelItem
|
||||
key={model.id}
|
||||
model={model}
|
||||
isSelected={selectedModel?.id === model.id}
|
||||
onPeek={(model) => setPeekedModel(model)}
|
||||
onSelect={() => {
|
||||
setSelectedModel(model)
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</HoverCard>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelItemProps {
|
||||
model: Model
|
||||
isSelected: boolean
|
||||
onSelect: () => void
|
||||
onPeek: (model: Model) => void
|
||||
}
|
||||
|
||||
function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
useMutationObserver(ref, (mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "aria-selected" &&
|
||||
ref.current?.getAttribute("aria-selected") === "true"
|
||||
) {
|
||||
onPeek(model)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={model.id}
|
||||
onSelect={onSelect}
|
||||
ref={ref}
|
||||
className="data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground"
|
||||
>
|
||||
{model.name}
|
||||
<Check
|
||||
className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")}
|
||||
/>
|
||||
</CommandItem>
|
||||
)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog } from "@radix-ui/react-dialog"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/registry/new-york-v4/ui/alert-dialog"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/registry/new-york-v4/ui/dialog"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
|
||||
export function PresetActions() {
|
||||
const [open, setIsOpen] = React.useState(false)
|
||||
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" size="icon">
|
||||
<span className="sr-only">Actions</span>
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onSelect={() => setIsOpen(true)}>
|
||||
Content filter preferences
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => setShowDeleteDialog(true)}
|
||||
className="text-red-600"
|
||||
>
|
||||
Delete preset
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Dialog open={open} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Content filter preferences</DialogTitle>
|
||||
<DialogDescription>
|
||||
The content filter flags text that may violate our content policy.
|
||||
It's powered by our moderation endpoint which is free to use
|
||||
to moderate your OpenAI API traffic. Learn more.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-6">
|
||||
<h4 className="text-muted-foreground text-sm">
|
||||
Playground Warnings
|
||||
</h4>
|
||||
<div className="flex items-start justify-between gap-4 pt-3">
|
||||
<Switch name="show" id="show" defaultChecked={true} />
|
||||
<Label className="grid gap-1 font-normal" htmlFor="show">
|
||||
<span className="font-semibold">
|
||||
Show a warning when content is flagged
|
||||
</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
A warning will be shown when sexual, hateful, violent or
|
||||
self-harm content is detected.
|
||||
</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">Close</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This preset will no longer be
|
||||
accessible by you or others you've shared it with.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
setShowDeleteDialog(false)
|
||||
toast.success("This preset has been deleted.")
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dialog"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||
|
||||
export function PresetSave() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary">Save</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Save preset</DialogTitle>
|
||||
<DialogDescription>
|
||||
This will save the current playground state as a preset which you
|
||||
can access later or share with others.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-6 py-4">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" autoFocus />
|
||||
</div>
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea id="description" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
import { Preset } from "../data/presets"
|
||||
|
||||
interface PresetSelectorProps extends PopoverProps {
|
||||
presets: Preset[]
|
||||
}
|
||||
|
||||
export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [selectedPreset, setSelectedPreset] = React.useState<Preset>()
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen} {...props}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-label="Load a preset..."
|
||||
aria-expanded={open}
|
||||
className="flex-1 justify-between md:max-w-[200px] lg:max-w-[300px]"
|
||||
>
|
||||
{selectedPreset ? selectedPreset.name : "Load a preset..."}
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search presets..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No presets found.</CommandEmpty>
|
||||
<CommandGroup heading="Examples">
|
||||
{presets.map((preset) => (
|
||||
<CommandItem
|
||||
key={preset.id}
|
||||
onSelect={() => {
|
||||
setSelectedPreset(preset)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{preset.name}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
selectedPreset?.id === preset.id
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem>More examples</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Copy } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
export function PresetShare() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="secondary">Share</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="flex w-[520px] flex-col gap-4">
|
||||
<div className="flex flex-col gap-1 text-center sm:text-left">
|
||||
<h3 className="text-lg font-semibold">Share preset</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Anyone who has this link and an OpenAI account will be able to view
|
||||
this.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative flex-1">
|
||||
<Label htmlFor="link" className="sr-only">
|
||||
Link
|
||||
</Label>
|
||||
<Input
|
||||
id="link"
|
||||
defaultValue="https://platform.openai.com/playground/p/7bbKYQvsVkNmVb8NGcdUOLae?model=text-davinci-003"
|
||||
readOnly
|
||||
className="h-9 pr-10"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="absolute top-1 right-1 size-7"
|
||||
>
|
||||
<span className="sr-only">Copy</span>
|
||||
<Copy className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface TemperatureSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
}
|
||||
|
||||
export function TemperatureSelector({
|
||||
defaultValue,
|
||||
}: TemperatureSelectorProps) {
|
||||
const [value, setValue] = React.useState(defaultValue)
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 pt-2">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="temperature"
|
||||
max={1}
|
||||
defaultValue={value}
|
||||
step={0.1}
|
||||
onValueChange={setValue}
|
||||
aria-label="Temperature"
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
align="start"
|
||||
className="w-[260px] text-sm"
|
||||
side="left"
|
||||
>
|
||||
Controls randomness: lowering results in less random completions. As
|
||||
the temperature approaches zero, the model will become deterministic
|
||||
and repetitive.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { SliderProps } from "@radix-ui/react-slider"
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||
|
||||
interface TopPSelectorProps {
|
||||
defaultValue: SliderProps["defaultValue"]
|
||||
}
|
||||
|
||||
export function TopPSelector({ defaultValue }: TopPSelectorProps) {
|
||||
const [value, setValue] = React.useState(defaultValue)
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 pt-2">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="top-p">Top P</Label>
|
||||
<span className="text-muted-foreground hover:border-border w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="top-p"
|
||||
max={1}
|
||||
defaultValue={value}
|
||||
step={0.1}
|
||||
onValueChange={setValue}
|
||||
aria-label="Top P"
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
align="start"
|
||||
className="w-[260px] text-sm"
|
||||
side="left"
|
||||
>
|
||||
Control diversity via nucleus sampling: 0.5 means half of all
|
||||
likelihood-weighted options are considered.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
export const types = ["GPT-3", "Codex"] as const
|
||||
|
||||
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>[] = [
|
||||
{
|
||||
id: "c305f976-8e38-42b1-9fb7-d21b2e34f0da",
|
||||
name: "text-davinci-003",
|
||||
description:
|
||||
"Most capable GPT-3 model. Can do any task the other models can do, often with higher quality, longer output and better instruction-following. Also supports inserting completions within text.",
|
||||
type: "GPT-3",
|
||||
strengths:
|
||||
"Complex intent, cause and effect, creative generation, search, summarization for audience",
|
||||
},
|
||||
{
|
||||
id: "464a47c3-7ab5-44d7-b669-f9cb5a9e8465",
|
||||
name: "text-curie-001",
|
||||
description: "Very capable, but faster and lower cost than Davinci.",
|
||||
type: "GPT-3",
|
||||
strengths:
|
||||
"Language translation, complex classification, sentiment, summarization",
|
||||
},
|
||||
{
|
||||
id: "ac0797b0-7e31-43b6-a494-da7e2ab43445",
|
||||
name: "text-babbage-001",
|
||||
description: "Capable of straightforward tasks, very fast, and lower cost.",
|
||||
type: "GPT-3",
|
||||
strengths: "Moderate classification, semantic search",
|
||||
},
|
||||
{
|
||||
id: "be638fb1-973b-4471-a49c-290325085802",
|
||||
name: "text-ada-001",
|
||||
description:
|
||||
"Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.",
|
||||
type: "GPT-3",
|
||||
strengths:
|
||||
"Parsing text, simple classification, address correction, keywords",
|
||||
},
|
||||
{
|
||||
id: "b43c0ea9-5ad4-456a-ae29-26cd77b6d0fb",
|
||||
name: "code-davinci-002",
|
||||
description:
|
||||
"Most capable Codex model. Particularly good at translating natural language to code. In addition to completing code, also supports inserting completions within code.",
|
||||
type: "Codex",
|
||||
},
|
||||
{
|
||||
id: "bbd57291-4622-4a21-9eed-dd6bd786fdd1",
|
||||
name: "code-cushman-001",
|
||||
description:
|
||||
"Almost as capable as Davinci Codex, but slightly faster. This speed advantage may make it preferable for real-time applications.",
|
||||
type: "Codex",
|
||||
strengths: "Real-time application where low-latency is preferable",
|
||||
},
|
||||
]
|
||||
@@ -1,47 +0,0 @@
|
||||
export interface Preset {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const presets: Preset[] = [
|
||||
{
|
||||
id: "9cb0e66a-9937-465d-a188-2c4c4ae2401f",
|
||||
name: "Grammatical Standard English",
|
||||
},
|
||||
{
|
||||
id: "61eb0e32-2391-4cd3-adc3-66efe09bc0b7",
|
||||
name: "Summarize for a 2nd grader",
|
||||
},
|
||||
{
|
||||
id: "a4e1fa51-f4ce-4e45-892c-224030a00bdd",
|
||||
name: "Text to command",
|
||||
},
|
||||
{
|
||||
id: "cc198b13-4933-43aa-977e-dcd95fa30770",
|
||||
name: "Q&A",
|
||||
},
|
||||
{
|
||||
id: "adfa95be-a575-45fd-a9ef-ea45386c64de",
|
||||
name: "English to other languages",
|
||||
},
|
||||
{
|
||||
id: "c569a06a-0bd6-43a7-adf9-bf68c09e7a79",
|
||||
name: "Parse unstructured data",
|
||||
},
|
||||
{
|
||||
id: "15ccc0d7-f37a-4f0a-8163-a37e162877dc",
|
||||
name: "Classification",
|
||||
},
|
||||
{
|
||||
id: "4641ef41-1c0f-421d-b4b2-70fe431081f3",
|
||||
name: "Natural language to Python",
|
||||
},
|
||||
{
|
||||
id: "48d34082-72f3-4a1b-a14d-f15aca4f57a0",
|
||||
name: "Explain code",
|
||||
},
|
||||
{
|
||||
id: "dfd42fd5-0394-4810-92c6-cc907d3bfd1a",
|
||||
name: "Chat",
|
||||
},
|
||||
]
|
||||
@@ -1,332 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { RotateCcw } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/registry/new-york-v4/ui/hover-card"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||
|
||||
import { CodeViewer } from "./components/code-viewer"
|
||||
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"
|
||||
|
||||
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 flex-1 flex-col md:flex">
|
||||
<div className="container flex flex-col items-start justify-between gap-2 py-4 sm:flex-row sm:items-center sm:gap-0 md:h-16">
|
||||
<h2 className="pl-0.5 text-lg font-semibold">Playground</h2>
|
||||
<div className="ml-auto flex w-full gap-2 sm:justify-end">
|
||||
<PresetSelector presets={presets} />
|
||||
<PresetSave />
|
||||
<div className="hidden gap-2 md:flex">
|
||||
<CodeViewer />
|
||||
<PresetShare />
|
||||
</div>
|
||||
<PresetActions />
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<Tabs defaultValue="complete" className="flex flex-1 flex-col">
|
||||
<div className="container flex flex-1 flex-col py-6">
|
||||
<div className="grid flex-1 items-stretch gap-6 md:grid-cols-[1fr_200px]">
|
||||
<div className="hidden flex-col gap-6 sm:flex md:order-2">
|
||||
<div className="grid gap-3">
|
||||
<HoverCard openDelay={200}>
|
||||
<HoverCardTrigger asChild>
|
||||
<span className="text-sm leading-none font-medium 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 w-full grid-cols-3">
|
||||
<TabsTrigger value="complete">
|
||||
<span className="sr-only">Complete</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<rect
|
||||
x="4"
|
||||
y="3"
|
||||
width="12"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="7"
|
||||
width="12"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="11"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="15"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="8.5"
|
||||
y="11"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="8.5"
|
||||
y="15"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="13"
|
||||
y="11"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
</svg>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="insert">
|
||||
<span className="sr-only">Insert</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
className="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.491 7.769a.888.888 0 0 1 .287.648.888.888 0 0 1-.287.648l-3.916 3.667a1.013 1.013 0 0 1-.692.268c-.26 0-.509-.097-.692-.268L5.275 9.065A.886.886 0 0 1 5 8.42a.889.889 0 0 1 .287-.64c.181-.17.427-.267.683-.269.257-.002.504.09.69.258L8.903 9.87V3.917c0-.243.103-.477.287-.649.183-.171.432-.268.692-.268.26 0 .509.097.692.268a.888.888 0 0 1 .287.649V9.87l2.245-2.102c.183-.172.432-.269.692-.269.26 0 .508.097.692.269Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<rect
|
||||
x="4"
|
||||
y="15"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="8.5"
|
||||
y="15"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="13"
|
||||
y="15"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
</svg>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="edit">
|
||||
<span className="sr-only">Edit</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
className="h-5 w-5"
|
||||
>
|
||||
<rect
|
||||
x="4"
|
||||
y="3"
|
||||
width="12"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="7"
|
||||
width="12"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="11"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="4"
|
||||
y="15"
|
||||
width="4"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<rect
|
||||
x="8.5"
|
||||
y="11"
|
||||
width="3"
|
||||
height="2"
|
||||
rx="1"
|
||||
fill="currentColor"
|
||||
></rect>
|
||||
<path
|
||||
d="M17.154 11.346a1.182 1.182 0 0 0-1.671 0L11 15.829V17.5h1.671l4.483-4.483a1.182 1.182 0 0 0 0-1.671Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<ModelSelector types={types} models={models} />
|
||||
<TemperatureSelector defaultValue={[0.56]} />
|
||||
<MaxLengthSelector defaultValue={[256]} />
|
||||
<TopPSelector defaultValue={[0.9]} />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col *:data-[slot=tab-content]:flex-1 md:order-1">
|
||||
<TabsContent value="complete" className="mt-0 border-0 p-0">
|
||||
<div className="flex h-full flex-col gap-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 gap-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="insert"
|
||||
className="mt-0 flex flex-col gap-4 border-0 p-0"
|
||||
>
|
||||
<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] p-4 lg:min-h-[700px] xl:min-h-[700px]"
|
||||
/>
|
||||
<div className="bg-muted rounded-md border"></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="edit"
|
||||
className="mt-0 flex flex-col gap-4 border-0 p-0"
|
||||
>
|
||||
<div className="grid h-full gap-6 lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Label htmlFor="input" className="sr-only">
|
||||
Input
|
||||
</Label>
|
||||
<Textarea
|
||||
id="input"
|
||||
placeholder="We is going to the market."
|
||||
className="flex-1 p-4 lg:min-h-[580px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="instructions">Instructions</Label>
|
||||
<Textarea
|
||||
id="instructions"
|
||||
placeholder="Fix the grammar."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted min-h-[400px] rounded-md border lg:min-h-[700px]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Checkbox } from "@/registry/new-york-v4/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() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
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 gap-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 gap-2">
|
||||
{status.icon && (
|
||||
<status.icon className="text-muted-foreground size-4" />
|
||||
)}
|
||||
<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 gap-2">
|
||||
{priority.icon && (
|
||||
<priority.icon className="text-muted-foreground size-4" />
|
||||
)}
|
||||
<span>{priority.label}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => <DataTableRowActions row={row} />,
|
||||
},
|
||||
]
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/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 gap-2", className)}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="data-[state=open]:bg-accent -ml-3 h-8"
|
||||
>
|
||||
<span>{title}</span>
|
||||
{column.getIsSorted() === "desc" ? (
|
||||
<ArrowDown />
|
||||
) : column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp />
|
||||
) : (
|
||||
<ChevronsUpDown />
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
||||
<ArrowUp />
|
||||
Asc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
||||
<ArrowDown />
|
||||
Desc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
||||
<EyeOff />
|
||||
Hide
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { Check, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
|
||||
interface DataTableFacetedFilterProps<TData, TValue> {
|
||||
column?: Column<TData, TValue>
|
||||
title?: string
|
||||
options: {
|
||||
label: string
|
||||
value: string
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
}[]
|
||||
}
|
||||
|
||||
export function DataTableFacetedFilter<TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
options,
|
||||
}: DataTableFacetedFilterProps<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 />
|
||||
{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 gap-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(
|
||||
"flex size-4 items-center justify-center rounded-[4px] border",
|
||||
isSelected
|
||||
? "bg-primary border-primary text-primary-foreground"
|
||||
: "border-input [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<Check className="text-primary-foreground size-3.5" />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="text-muted-foreground size-4" />
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
{facets?.get(option.value) && (
|
||||
<span className="text-muted-foreground ml-auto flex size-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>
|
||||
)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/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="text-muted-foreground flex-1 text-sm">
|
||||
{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, 25, 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"
|
||||
size="icon"
|
||||
className="hidden size-8 lg:flex"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<ChevronsLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<ChevronRight />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="hidden size-8 lg:flex"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<ChevronsRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Row } from "@tanstack/react-table"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/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"
|
||||
size="icon"
|
||||
className="data-[state=open]:bg-muted size-8"
|
||||
>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[160px]">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>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 variant="destructive">
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { DataTableViewOptions } from "@/app/(app)/examples/tasks/components/data-table-view-options"
|
||||
|
||||
import { priorities, statuses } from "../data/data"
|
||||
import { DataTableFacetedFilter } from "./data-table-faceted-filter"
|
||||
|
||||
interface DataTableToolbarProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
export function DataTableToolbar<TData>({
|
||||
table,
|
||||
}: DataTableToolbarProps<TData>) {
|
||||
const isFiltered = table.getState().columnFilters.length > 0
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center gap-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"
|
||||
size="sm"
|
||||
onClick={() => table.resetColumnFilters()}
|
||||
>
|
||||
Reset
|
||||
<X />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<DataTableViewOptions table={table} />
|
||||
<Button size="sm">Add Task</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { Settings2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function DataTableViewOptions<TData>({
|
||||
table,
|
||||
}: {
|
||||
table: Table<TData>
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-auto hidden h-8 lg:flex"
|
||||
>
|
||||
<Settings2 />
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
VisibilityState,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york-v4/ui/table"
|
||||
|
||||
import { DataTablePagination } from "./data-table-pagination"
|
||||
import { DataTableToolbar } from "./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,
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: 25,
|
||||
},
|
||||
},
|
||||
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="flex flex-col gap-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} colSpan={header.colSpan}>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/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 leading-none font-medium">shadcn</p>
|
||||
<p className="text-muted-foreground text-xs leading-none">
|
||||
m@example.com
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
Profile
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Billing
|
||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Settings
|
||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>New Team</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
Log out
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
CheckCircle,
|
||||
Circle,
|
||||
CircleOff,
|
||||
HelpCircle,
|
||||
Timer,
|
||||
} 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: Timer,
|
||||
},
|
||||
{
|
||||
value: "done",
|
||||
label: "Done",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
value: "canceled",
|
||||
label: "Canceled",
|
||||
icon: CircleOff,
|
||||
},
|
||||
]
|
||||
|
||||
export const priorities = [
|
||||
{
|
||||
label: "Low",
|
||||
value: "low",
|
||||
icon: ArrowDown,
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium",
|
||||
icon: ArrowRight,
|
||||
},
|
||||
{
|
||||
label: "High",
|
||||
value: "high",
|
||||
icon: ArrowUp,
|
||||
},
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
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>
|
||||
@@ -1,20 +0,0 @@
|
||||
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.number.int({ 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.")
|
||||
@@ -1,702 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
@@ -1,67 +0,0 @@
|
||||
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/(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 gap-8 p-8 md:flex">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Here's a list of your tasks for this month.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable data={tasks} columns={columns} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
14
apps/v4/app/(app)/forms/page.tsx
Normal file
14
apps/v4/app/(app)/forms/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { FormsDemo } from "@/components/forms-demo"
|
||||
|
||||
export default function FormsPage() {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
|
||||
<div className="">
|
||||
<FormsDemo />
|
||||
</div>
|
||||
<div className="theme-scaled">
|
||||
<FormsDemo />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,44 @@
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||
import { NavHeader } from "@/components/nav-header"
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export default async function AppLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-background relative z-10 flex min-h-svh flex-col">
|
||||
<SiteHeader />
|
||||
<main className="flex flex-1 flex-col">{children}</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<div className="flex h-14 w-full items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1.5" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<NavHeader />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<ThemeSelector />
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { NextResponse, type NextRequest } from "next/server"
|
||||
|
||||
import { source } from "@/lib/source"
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string[] }> }
|
||||
) {
|
||||
const slug = (await params).slug
|
||||
const page = source.getPage(slug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
// @ts-expect-error - revisit fumadocs types.
|
||||
return new NextResponse(page.data.content, {
|
||||
headers: {
|
||||
"Content-Type": "text/markdown; charset=utf-8",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
40
apps/v4/app/(app)/login/page.tsx
Normal file
40
apps/v4/app/(app)/login/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Manrope as FontManrope,
|
||||
Lexend as FontSans,
|
||||
Newsreader as FontSerif,
|
||||
} from "next/font/google"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { LoginForm } from "@/components/login-form"
|
||||
|
||||
const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" })
|
||||
const fontSerif = FontSerif({ subsets: ["latin"], variable: "--font-serif" })
|
||||
const fontManrope = FontManrope({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-manrope",
|
||||
})
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-muted dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
||||
fontSans.variable,
|
||||
fontSerif.variable,
|
||||
fontManrope.variable
|
||||
)}
|
||||
>
|
||||
<div className="w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div className="theme-login-one w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1482872376051-5ce74ebf0908?q=80&w=3050&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-two w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1498758536662-35b82cd15e29?q=80&w=3088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
<div className="theme-login-three w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1536147116438-62679a5e01f2?q=80&w=2688&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
196
apps/v4/app/(app)/page.tsx
Normal file
196
apps/v4/app/(app)/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import { AccordionDemo } from "@/components/accordion-demo"
|
||||
import { AlertDemo } from "@/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/components/alert-dialog-demo"
|
||||
import { AspectRatioDemo } from "@/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/components/badge-demo"
|
||||
import { BreadcrumbDemo } from "@/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/components/button-demo"
|
||||
import { CalendarDemo } from "@/components/calendar-demo"
|
||||
import { CardDemo } from "@/components/card-demo"
|
||||
import { CarouselDemo } from "@/components/carousel-demo"
|
||||
import { ChartDemo } from "@/components/chart-demo"
|
||||
import { CheckboxDemo } from "@/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/components/combobox-demo"
|
||||
import { CommandDemo } from "@/components/command-demo"
|
||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/components/dropdown-menu-demo"
|
||||
import { FormDemo } from "@/components/form-demo"
|
||||
import { HoverCardDemo } from "@/components/hover-card-demo"
|
||||
import { InputDemo } from "@/components/input-demo"
|
||||
import { InputOTPDemo } from "@/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/components/label-demo"
|
||||
import { MenubarDemo } from "@/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/components/popover-demo"
|
||||
import { ProgressDemo } from "@/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/components/radio-group-demo"
|
||||
import { ResizableDemo } from "@/components/resizable-demo"
|
||||
import { ScrollAreaDemo } from "@/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/components/select-demo"
|
||||
import { SeparatorDemo } from "@/components/separator-demo"
|
||||
import { SheetDemo } from "@/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/components/slider-demo"
|
||||
import { SonnerDemo } from "@/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/components/switch-demo"
|
||||
import { TableDemo } from "@/components/table-demo"
|
||||
import { TabsDemo } from "@/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/components/textarea-demo"
|
||||
import { ToggleDemo } from "@/components/toggle-demo"
|
||||
import { ToggleGroupDemo } from "@/components/toggle-group-demo"
|
||||
import { TooltipDemo } from "@/components/tooltip-demo"
|
||||
|
||||
export default function SinkPage() {
|
||||
return (
|
||||
<div className="@container grid flex-1 gap-4 p-4">
|
||||
<ComponentWrapper name="chart" className="w-full">
|
||||
<ChartDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert-dialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="aspect-ratio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="badge">
|
||||
<BadgeDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="button">
|
||||
<ButtonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="card">
|
||||
<CardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="carousel">
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="command">
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="context-menu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="date-picker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dropdown-menu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="form">
|
||||
<FormDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="hover-card">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input-otp">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="navigation-menu">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="radio-group">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="resizable">
|
||||
<ResizableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="scroll-area">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="table">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle">
|
||||
<ToggleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle-group">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
const title = "Pick a Color. Make it yours."
|
||||
const description =
|
||||
"Try our hand-picked themes. Copy and paste them into your project. New theme editor coming soon."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
title
|
||||
)}&description=${encodeURIComponent(description)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function ThemesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<a href="#themes">Browse Themes</a>
|
||||
</Button>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/docs/theming">Documentation</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CardsDemo } from "@/components/cards"
|
||||
import { ThemeCustomizer } from "@/components/theme-customizer"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
export default function ThemesPage() {
|
||||
return (
|
||||
<>
|
||||
<div id="themes" className="container-wrapper scroll-mt-20">
|
||||
<div className="container flex items-center justify-between gap-8 px-6 py-4 md:px-8">
|
||||
<ThemeCustomizer />
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
|
||||
<div className="theme-container container flex flex-1 flex-col">
|
||||
<CardsDemo />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard-03/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard-03/components/site-header"
|
||||
|
||||
import "../../themes.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
closestCenter,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
} from "@dnd-kit/core"
|
||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
arrayMove,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable"
|
||||
@@ -37,6 +37,9 @@ import {
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
Row,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
@@ -44,10 +47,7 @@ import {
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
Row,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
VisibilityState,
|
||||
} from "@tanstack/react-table"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { toast } from "sonner"
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
import { Clock2Icon } from "lucide-react"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
import { es } from "react-day-picker/locale"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Calendar, CalendarDayButton } from "@/registry/new-york-v4/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/registry/new-york-v4/ui/card"
|
||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
|
||||
export function CalendarDemo() {
|
||||
return (
|
||||
<div className="bg-muted flex flex-1 flex-col flex-wrap justify-center gap-8 p-10 lg:flex-row">
|
||||
<CalendarSingle />
|
||||
<CalendarMultiple />
|
||||
<CalendarRange />
|
||||
<CalendarBookedDates />
|
||||
<CalendarRangeMultipleMonths />
|
||||
<CalendarWithTime />
|
||||
<CalendarWithPresets />
|
||||
<CalendarCustomDays />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarSingle() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
|
||||
)
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">Single Selection</div>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border shadow-sm"
|
||||
captionLayout="dropdown"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarMultiple() {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">Multiple Selection</div>
|
||||
<Calendar mode="multiple" className="rounded-lg border shadow-sm" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarRange() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">Range Selection</div>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarRangeMultipleMonths() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 3, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 3, 12), 60),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">Range Selection + Locale</div>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={3}
|
||||
locale={es}
|
||||
fixedWeeks
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarBookedDates() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), 1, 3)
|
||||
)
|
||||
const bookedDates = Array.from(
|
||||
{ length: 15 },
|
||||
(_, i) => new Date(new Date().getFullYear(), new Date().getMonth(), 12 + i)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">With booked dates</div>
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={bookedDates}
|
||||
modifiers={{
|
||||
booked: bookedDates,
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
booked: "[&>button]:line-through opacity-100",
|
||||
}}
|
||||
className="rounded-lg border shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarWithTime() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">With Time Input</div>
|
||||
<Card className="w-fit py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="p-0"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-3 border-t px-4 pt-4">
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<Label htmlFor="time-from">Start Time</Label>
|
||||
<div className="relative flex w-full items-center gap-2">
|
||||
<Clock2Icon className="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none" />
|
||||
<Input
|
||||
id="time-from"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="10:30:00"
|
||||
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<Label htmlFor="time-to">End Time</Label>
|
||||
<div className="relative flex w-full items-center gap-2">
|
||||
<Clock2Icon className="text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none" />
|
||||
<Input
|
||||
id="time-to"
|
||||
type="time"
|
||||
step="1"
|
||||
defaultValue="12:30:00"
|
||||
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarCustomDays() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 11, 8),
|
||||
to: addDays(new Date(new Date().getFullYear(), 11, 8), 10),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">
|
||||
With Custom Days and Formatters
|
||||
</div>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={1}
|
||||
captionLayout="dropdown"
|
||||
className="rounded-lg border shadow-sm [--cell-size:--spacing(12)]"
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) => {
|
||||
return date.toLocaleString("default", { month: "long" })
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
DayButton: ({ children, modifiers, day, ...props }) => {
|
||||
const isWeekend = day.date.getDay() === 0 || day.date.getDay() === 6
|
||||
|
||||
return (
|
||||
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
|
||||
{children}
|
||||
{!modifiers.outside && (
|
||||
<span>{isWeekend ? "$120" : "$100"}</span>
|
||||
)}
|
||||
</CalendarDayButton>
|
||||
)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarWithPresets() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), 1, 12)
|
||||
)
|
||||
const [currentMonth, setCurrentMonth] = React.useState<Date>(
|
||||
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex max-w-[300px] flex-col gap-3">
|
||||
<div className="px-2 text-center text-sm">With Presets</div>
|
||||
<Card className="w-fit py-4">
|
||||
<CardContent className="px-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
month={currentMonth}
|
||||
onMonthChange={setCurrentMonth}
|
||||
fixedWeeks
|
||||
className="p-0 [--cell-size:--spacing(9.5)]"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-2 border-t px-4 pt-4">
|
||||
{[
|
||||
{ label: "Today", value: 0 },
|
||||
{ label: "Tomorrow", value: 1 },
|
||||
{ label: "In 3 days", value: 3 },
|
||||
{ label: "In a week", value: 7 },
|
||||
{ label: "In 2 weeks", value: 14 },
|
||||
].map((preset) => (
|
||||
<Button
|
||||
key={preset.value}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
const newDate = addDays(new Date(), preset.value)
|
||||
setDate(newDate)
|
||||
setCurrentMonth(
|
||||
new Date(newDate.getFullYear(), newDate.getMonth(), 1)
|
||||
)
|
||||
}}
|
||||
>
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/registry/new-york-v4/lib/utils"
|
||||
|
||||
export function ComponentWrapper({
|
||||
className,
|
||||
name,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"div"> & { name: string }) {
|
||||
return (
|
||||
<ComponentErrorBoundary name={name}>
|
||||
<div
|
||||
id={name}
|
||||
data-name={name.toLowerCase()}
|
||||
className={cn(
|
||||
"flex w-full scroll-mt-16 flex-col rounded-lg border",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="border-b px-4 py-3">
|
||||
<div className="text-sm font-medium">{getComponentName(name)}</div>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center gap-2 p-4">{children}</div>
|
||||
</div>
|
||||
</ComponentErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
class ComponentErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode; name: string },
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: { children: React.ReactNode; name: string }) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error(`Error in component ${this.props.name}:`, error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="p-4 text-red-500">
|
||||
Something went wrong in component: {this.props.name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
function getComponentName(name: string) {
|
||||
// convert kebab-case to title case
|
||||
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AccordionDemo } from "@/app/(internal)/sink/components/accordion-demo"
|
||||
import { AlertDemo } from "@/app/(internal)/sink/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/app/(internal)/sink/components/alert-dialog-demo"
|
||||
import { AppSidebar } from "@/app/(internal)/sink/components/app-sidebar"
|
||||
import { AspectRatioDemo } from "@/app/(internal)/sink/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/app/(internal)/sink/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/app/(internal)/sink/components/badge-demo"
|
||||
import { BreadcrumbDemo } from "@/app/(internal)/sink/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/app/(internal)/sink/components/button-demo"
|
||||
import { CalendarDemo } from "@/app/(internal)/sink/components/calendar-demo"
|
||||
import { CardDemo } from "@/app/(internal)/sink/components/card-demo"
|
||||
import { CarouselDemo } from "@/app/(internal)/sink/components/carousel-demo"
|
||||
import { ChartDemo } from "@/app/(internal)/sink/components/chart-demo"
|
||||
import { CheckboxDemo } from "@/app/(internal)/sink/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/app/(internal)/sink/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/app/(internal)/sink/components/combobox-demo"
|
||||
import { CommandDemo } from "@/app/(internal)/sink/components/command-demo"
|
||||
import { ComponentWrapper } from "@/app/(internal)/sink/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/app/(internal)/sink/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/app/(internal)/sink/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/app/(internal)/sink/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/app/(internal)/sink/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/app/(internal)/sink/components/dropdown-menu-demo"
|
||||
import { FormDemo } from "@/app/(internal)/sink/components/form-demo"
|
||||
import { HoverCardDemo } from "@/app/(internal)/sink/components/hover-card-demo"
|
||||
import { InputDemo } from "@/app/(internal)/sink/components/input-demo"
|
||||
import { InputOTPDemo } from "@/app/(internal)/sink/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/app/(internal)/sink/components/label-demo"
|
||||
import { MenubarDemo } from "@/app/(internal)/sink/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/app/(internal)/sink/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/app/(internal)/sink/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/app/(internal)/sink/components/popover-demo"
|
||||
import { ProgressDemo } from "@/app/(internal)/sink/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/app/(internal)/sink/components/radio-group-demo"
|
||||
import { ResizableDemo } from "@/app/(internal)/sink/components/resizable-demo"
|
||||
import { ScrollAreaDemo } from "@/app/(internal)/sink/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/app/(internal)/sink/components/select-demo"
|
||||
import { SeparatorDemo } from "@/app/(internal)/sink/components/separator-demo"
|
||||
import { SheetDemo } from "@/app/(internal)/sink/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/app/(internal)/sink/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/app/(internal)/sink/components/slider-demo"
|
||||
import { SonnerDemo } from "@/app/(internal)/sink/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/app/(internal)/sink/components/switch-demo"
|
||||
import { TableDemo } from "@/app/(internal)/sink/components/table-demo"
|
||||
import { TabsDemo } from "@/app/(internal)/sink/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/app/(internal)/sink/components/textarea-demo"
|
||||
import { ToggleDemo } from "@/app/(internal)/sink/components/toggle-demo"
|
||||
import { ToggleGroupDemo } from "@/app/(internal)/sink/components/toggle-group-demo"
|
||||
import { TooltipDemo } from "@/app/(internal)/sink/components/tooltip-demo"
|
||||
|
||||
export const dynamic = "force-static"
|
||||
export const revalidate = false
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Kitchen Sink",
|
||||
description: "A page with all components for testing purposes.",
|
||||
}
|
||||
|
||||
export default async function SinkPage() {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider defaultOpen={defaultOpen} className="theme-container">
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="bg-background sticky top-0 z-10 flex h-14 items-center border-b p-4">
|
||||
<SidebarTrigger />
|
||||
<Separator orientation="vertical" className="mr-4 ml-2 !h-4" />
|
||||
<h1 className="text-base font-medium">Kitchen Sink</h1>
|
||||
<ThemeSelector className="ml-auto" />
|
||||
</header>
|
||||
<div className="@container grid flex-1 gap-4 p-4">
|
||||
<ComponentWrapper name="accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="alert-dialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="aspect-ratio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="badge">
|
||||
<BadgeDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="button">
|
||||
<ButtonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="card">
|
||||
<CardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="carousel">
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="chart" className="w-full">
|
||||
<ChartDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="command">
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="context-menu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="date-picker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="dropdown-menu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="form">
|
||||
<FormDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="hover-card">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="input-otp">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="navigation-menu">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="radio-group">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="resizable">
|
||||
<ResizableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="scroll-area">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="table">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle">
|
||||
<ToggleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="toggle-group">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -4,13 +4,9 @@ import { notFound } from "next/navigation"
|
||||
import { registryItemSchema } from "shadcn/registry"
|
||||
import { z } from "zod"
|
||||
|
||||
import { siteConfig } from "@/lib/config"
|
||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
export const dynamicParams = false
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
@@ -34,13 +30,13 @@ export async function generateMetadata({
|
||||
const description = item.description
|
||||
|
||||
return {
|
||||
title: item.description,
|
||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "article",
|
||||
url: absoluteUrl(`/view/${item.name}`),
|
||||
url: absoluteUrl(`/blocks/${item.name}`),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
@@ -60,18 +56,15 @@ export async function generateMetadata({
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const { Index } = await import("@/registry/__index__")
|
||||
const { Index } = await import("@/__registry__")
|
||||
const index = z.record(registryItemSchema).parse(Index)
|
||||
|
||||
return Object.values(index)
|
||||
.filter((block) =>
|
||||
[
|
||||
"registry:block",
|
||||
"registry:component",
|
||||
"registry:example",
|
||||
"registry:internal",
|
||||
].includes(block.type)
|
||||
["registry:block", "registry:component"].includes(block.type)
|
||||
)
|
||||
.map((block) => ({
|
||||
name: block.name,
|
||||
@@ -95,7 +88,7 @@ export default async function BlockPage({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("bg-background", item.meta?.container)}>
|
||||
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
|
||||
<Component />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,54 +1,13 @@
|
||||
---
|
||||
title: Manual Installation
|
||||
description: Add dependencies to your project manually.
|
||||
---
|
||||
|
||||
<Steps>
|
||||
|
||||
### Add Tailwind CSS
|
||||
|
||||
Components are styled using Tailwind CSS. You need to install Tailwind CSS in your project.
|
||||
|
||||
[Follow the Tailwind CSS installation instructions to get started.](https://tailwindcss.com/docs/installation)
|
||||
|
||||
### Add dependencies
|
||||
|
||||
Add the following dependencies to your project:
|
||||
|
||||
```bash
|
||||
npm install class-variance-authority clsx tailwind-merge lucide-react tw-animate-css
|
||||
```
|
||||
|
||||
### Configure path aliases
|
||||
|
||||
Configure the path aliases in your `tsconfig.json` file.
|
||||
|
||||
```json {3-6} title="tsconfig.json" showLineNumbers
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `@` alias is a preference. You can use other aliases if you want.
|
||||
|
||||
### Configure styles
|
||||
|
||||
Add the following to your styles/globals.css file. You can learn more about using CSS variables for theming in the [theming section](/docs/theming).
|
||||
|
||||
<CodeCollapsibleWrapper>
|
||||
|
||||
```css showLineNumbers title="src/styles/globals.css"
|
||||
@import "tailwindcss";
|
||||
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@import "./themes.css";
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
@@ -64,7 +23,6 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
@@ -73,7 +31,6 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
@@ -87,23 +44,22 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover: oklch(0.269 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent: oklch(0.371 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.439 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
@@ -115,11 +71,15 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
@@ -135,7 +95,6 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
@@ -144,10 +103,6 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
@@ -166,51 +121,3 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeCollapsibleWrapper>
|
||||
|
||||
### Add a cn helper
|
||||
|
||||
```ts showLineNumbers title="lib/utils.ts"
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
```
|
||||
|
||||
### Create a `components.json` file
|
||||
|
||||
Create a `components.json` file in the root of your project.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
```
|
||||
|
||||
### That's it
|
||||
|
||||
You can now start adding components to your project.
|
||||
|
||||
</Steps>
|
||||
@@ -1,25 +1,35 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { Metadata, Viewport } from "next"
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
|
||||
import { fontVariables } from "@/lib/fonts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { LayoutProvider } from "@/hooks/use-layout"
|
||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
||||
import { siteConfig } from "@/www/config/site"
|
||||
|
||||
import "@/styles/globals.css"
|
||||
import "./globals.css"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
||||
|
||||
const META_THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#09090b",
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: siteConfig.name,
|
||||
template: `%s - ${siteConfig.name}`,
|
||||
},
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL!),
|
||||
metadataBase: new URL("https://v4.shadcn.com"),
|
||||
description: siteConfig.description,
|
||||
keywords: ["Next.js", "React", "Tailwind CSS", "Components", "shadcn"],
|
||||
keywords: [
|
||||
"Next.js",
|
||||
"React",
|
||||
"Tailwind CSS",
|
||||
"Server Components",
|
||||
"Radix UI",
|
||||
],
|
||||
authors: [
|
||||
{
|
||||
name: "shadcn",
|
||||
@@ -30,13 +40,13 @@ export const metadata: Metadata = {
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: process.env.NEXT_PUBLIC_APP_URL!,
|
||||
url: "https://v4.shadcn.com",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
siteName: siteConfig.name,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_APP_URL}/opengraph-image.png`,
|
||||
url: "https://v4.shadcn.com/opengraph-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
@@ -47,7 +57,7 @@ export const metadata: Metadata = {
|
||||
card: "summary_large_image",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
images: [`${process.env.NEXT_PUBLIC_APP_URL}/opengraph-image.png`],
|
||||
images: ["https://v4.shadcn.com/opengraph-image.png"],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
icons: {
|
||||
@@ -58,11 +68,19 @@ export const metadata: Metadata = {
|
||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
export const viewport: Viewport = {
|
||||
themeColor: META_THEME_COLORS.light,
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
const cookieStore = await cookies()
|
||||
const activeThemeValue = cookieStore.get("active_theme")?.value
|
||||
const isScaled = activeThemeValue?.endsWith("-scaled")
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
@@ -73,30 +91,31 @@ export default function RootLayout({
|
||||
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||
}
|
||||
if (localStorage.layout) {
|
||||
document.documentElement.classList.add('layout-' + localStorage.layout)
|
||||
}
|
||||
} catch (_) {}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<meta name="theme-color" content={META_THEME_COLORS.light} />
|
||||
</head>
|
||||
<body
|
||||
className={cn(
|
||||
"text-foreground group/body overscroll-none font-sans antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
|
||||
"bg-background overscroll-none font-sans antialiased",
|
||||
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
||||
isScaled ? "theme-scaled" : "",
|
||||
fontVariables
|
||||
)}
|
||||
>
|
||||
<ThemeProvider>
|
||||
<LayoutProvider>
|
||||
<ActiveThemeProvider>
|
||||
{children}
|
||||
<TailwindIndicator />
|
||||
<Toaster position="top-center" />
|
||||
<Analytics />
|
||||
</ActiveThemeProvider>
|
||||
</LayoutProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme
|
||||
>
|
||||
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
||||
{children}
|
||||
<Toaster />
|
||||
<Analytics />
|
||||
</ActiveThemeProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
|
||||
import { ImageResponse } from "next/og"
|
||||
|
||||
async function loadAssets(): Promise<
|
||||
{ name: string; data: Buffer; weight: 400 | 600; style: "normal" }[]
|
||||
> {
|
||||
const [
|
||||
{ base64Font: normal },
|
||||
{ base64Font: mono },
|
||||
{ base64Font: semibold },
|
||||
] = await Promise.all([
|
||||
import("./geist-regular-otf.json").then((mod) => mod.default || mod),
|
||||
import("./geistmono-regular-otf.json").then((mod) => mod.default || mod),
|
||||
import("./geist-semibold-otf.json").then((mod) => mod.default || mod),
|
||||
])
|
||||
|
||||
return [
|
||||
{
|
||||
name: "Geist",
|
||||
data: Buffer.from(normal, "base64"),
|
||||
weight: 400 as const,
|
||||
style: "normal" as const,
|
||||
},
|
||||
{
|
||||
name: "Geist Mono",
|
||||
data: Buffer.from(mono, "base64"),
|
||||
weight: 400 as const,
|
||||
style: "normal" as const,
|
||||
},
|
||||
{
|
||||
name: "Geist",
|
||||
data: Buffer.from(semibold, "base64"),
|
||||
weight: 600 as const,
|
||||
style: "normal" as const,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const title = searchParams.get("title")
|
||||
const description = searchParams.get("description")
|
||||
|
||||
const [fonts] = await Promise.all([loadAssets()])
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
tw="flex h-full w-full bg-black text-white"
|
||||
style={{ fontFamily: "Geist Sans" }}
|
||||
>
|
||||
<div tw="flex border absolute border-stone-700 border-dashed inset-y-0 left-16 w-[1px]" />
|
||||
<div tw="flex border absolute border-stone-700 border-dashed inset-y-0 right-16 w-[1px]" />
|
||||
<div tw="flex border absolute border-stone-700 inset-x-0 h-[1px] top-16" />
|
||||
<div tw="flex border absolute border-stone-700 inset-x-0 h-[1px] bottom-16" />
|
||||
<div tw="flex absolute flex-row bottom-24 right-24 text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
width={48}
|
||||
height={48}
|
||||
>
|
||||
<rect width="256" height="256" fill="none"></rect>
|
||||
<line
|
||||
x1="208"
|
||||
y1="128"
|
||||
x2="128"
|
||||
y2="208"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="32"
|
||||
></line>
|
||||
<line
|
||||
x1="192"
|
||||
y1="40"
|
||||
x2="40"
|
||||
y2="192"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="32"
|
||||
></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div tw="flex flex-col absolute w-[896px] justify-center inset-32">
|
||||
<div
|
||||
tw="tracking-tight flex-grow-1 flex flex-col justify-center leading-[1.1]"
|
||||
style={{
|
||||
textWrap: "balance",
|
||||
fontWeight: 600,
|
||||
fontSize: title && title.length > 20 ? 64 : 80,
|
||||
letterSpacing: "-0.04em",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<div
|
||||
tw="text-[40px] leading-[1.5] flex-grow-1 text-stone-400"
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
textWrap: "balance",
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 628,
|
||||
fonts,
|
||||
}
|
||||
)
|
||||
}
|
||||
364
apps/v4/app/themes.css
Normal file
364
apps/v4/app/themes.css
Normal file
@@ -0,0 +1,364 @@
|
||||
.theme-stone {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.147 0.004 49.25);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.147 0.004 49.25);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.147 0.004 49.25);
|
||||
--primary: oklch(0.216 0.006 56.043);
|
||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--secondary: oklch(0.97 0.001 106.424);
|
||||
--secondary-foreground: oklch(0.216 0.006 56.043);
|
||||
--muted: oklch(0.97 0.001 106.424);
|
||||
--muted-foreground: oklch(0.553 0.013 58.071);
|
||||
--accent: oklch(0.97 0.001 106.424);
|
||||
--accent-foreground: oklch(0.216 0.006 56.043);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.923 0.003 48.717);
|
||||
--input: oklch(0.923 0.003 48.717);
|
||||
--ring: oklch(0.709 0.01 56.259);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0.001 106.423);
|
||||
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
||||
--sidebar-primary: oklch(0.216 0.006 56.043);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.97 0.001 106.424);
|
||||
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
||||
--sidebar-border: oklch(0.923 0.003 48.717);
|
||||
--sidebar-ring: oklch(0.709 0.01 56.259);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.147 0.004 49.25);
|
||||
--foreground: oklch(0.985 0.001 106.423);
|
||||
--card: oklch(0.216 0.006 56.043);
|
||||
--card-foreground: oklch(0.985 0.001 106.423);
|
||||
--popover: oklch(0.216 0.006 56.043);
|
||||
--popover-foreground: oklch(0.985 0.001 106.423);
|
||||
--primary: oklch(0.923 0.003 48.717);
|
||||
--primary-foreground: oklch(0.216 0.006 56.043);
|
||||
--secondary: oklch(0.268 0.007 34.298);
|
||||
--secondary-foreground: oklch(0.985 0.001 106.423);
|
||||
--muted: oklch(0.268 0.007 34.298);
|
||||
--muted-foreground: oklch(0.709 0.01 56.259);
|
||||
--accent: oklch(0.268 0.007 34.298);
|
||||
--accent-foreground: oklch(0.985 0.001 106.423);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.553 0.013 58.071);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.216 0.006 56.043);
|
||||
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.268 0.007 34.298);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.553 0.013 58.071);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-zinc {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.21 0.006 285.885);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.705 0.015 286.067);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.92 0.004 286.32);
|
||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.552 0.016 285.938);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.006 285.885);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-neutral {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-gray {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.13 0.028 261.692);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.13 0.028 261.692);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.13 0.028 261.692);
|
||||
--primary: oklch(0.21 0.034 264.665);
|
||||
--primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--secondary: oklch(0.967 0.003 264.542);
|
||||
--secondary-foreground: oklch(0.21 0.034 264.665);
|
||||
--muted: oklch(0.967 0.003 264.542);
|
||||
--muted-foreground: oklch(0.551 0.027 264.364);
|
||||
--accent: oklch(0.967 0.003 264.542);
|
||||
--accent-foreground: oklch(0.21 0.034 264.665);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.928 0.006 264.531);
|
||||
--input: oklch(0.928 0.006 264.531);
|
||||
--ring: oklch(0.707 0.022 261.325);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0.002 247.839);
|
||||
--sidebar-foreground: oklch(0.13 0.028 261.692);
|
||||
--sidebar-primary: oklch(0.21 0.034 264.665);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-accent: oklch(0.967 0.003 264.542);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
|
||||
--sidebar-border: oklch(0.928 0.006 264.531);
|
||||
--sidebar-ring: oklch(0.707 0.022 261.325);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.13 0.028 261.692);
|
||||
--foreground: oklch(0.985 0.002 247.839);
|
||||
--card: oklch(0.21 0.034 264.665);
|
||||
--card-foreground: oklch(0.985 0.002 247.839);
|
||||
--popover: oklch(0.21 0.034 264.665);
|
||||
--popover-foreground: oklch(0.985 0.002 247.839);
|
||||
--primary: oklch(0.928 0.006 264.531);
|
||||
--primary-foreground: oklch(0.21 0.034 264.665);
|
||||
--secondary: oklch(0.278 0.033 256.848);
|
||||
--secondary-foreground: oklch(0.985 0.002 247.839);
|
||||
--muted: oklch(0.278 0.033 256.848);
|
||||
--muted-foreground: oklch(0.707 0.022 261.325);
|
||||
--accent: oklch(0.278 0.033 256.848);
|
||||
--accent-foreground: oklch(0.985 0.002 247.839);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.034 264.665);
|
||||
--sidebar-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-accent: oklch(0.278 0.033 256.848);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-slate {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.984 0.003 247.858);
|
||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||
|
||||
@variant dark {
|
||||
--background: oklch(0.129 0.042 264.695);
|
||||
--foreground: oklch(0.984 0.003 247.858);
|
||||
--card: oklch(0.208 0.042 265.755);
|
||||
--card-foreground: oklch(0.984 0.003 247.858);
|
||||
--popover: oklch(0.208 0.042 265.755);
|
||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
||||
--primary: oklch(0.929 0.013 255.508);
|
||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
||||
--secondary: oklch(0.279 0.041 260.031);
|
||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
||||
--muted: oklch(0.279 0.041 260.031);
|
||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
||||
--accent: oklch(0.279 0.041 260.031);
|
||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.208 0.042 265.755);
|
||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-scaled {
|
||||
@media (min-width: 1024px) {
|
||||
--radius: 0.6rem;
|
||||
--text-lg: 1.05rem;
|
||||
--text-base: 0.85rem;
|
||||
--text-sm: 0.8rem;
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
--spacing: 0.16rem;
|
||||
}
|
||||
|
||||
[data-slot="card-header"] *,
|
||||
[data-slot="card-content"] *,
|
||||
[data-slot="card-footer"] * {
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react"
|
||||
|
||||
const COOKIE_NAME = "active_theme"
|
||||
const DEFAULT_THEME = "default"
|
||||
|
||||
function setThemeCookie(theme: string) {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""}`
|
||||
}
|
||||
|
||||
type ThemeContextType = {
|
||||
activeTheme: string
|
||||
setActiveTheme: (theme: string) => void
|
||||
@@ -29,6 +36,8 @@ export function ActiveThemeProvider({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setThemeCookie(activeTheme)
|
||||
|
||||
Array.from(document.body.classList)
|
||||
.filter((className) => className.startsWith("theme-"))
|
||||
.forEach((className) => {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import Link from "next/link"
|
||||
import { ArrowRightIcon } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
|
||||
export function Announcement() {
|
||||
return (
|
||||
<Badge asChild variant="secondary" className="rounded-full">
|
||||
<Link href="/docs/components/calendar">
|
||||
New Calendar Component <ArrowRightIcon />
|
||||
</Link>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import { Index } from "@/__registry__"
|
||||
import {
|
||||
AudioWaveform,
|
||||
BookOpen,
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
SquareTerminal,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Index } from "@/registry/__index__"
|
||||
import { NavUser } from "@/registry/new-york-v4/blocks/sidebar-07/components/nav-user"
|
||||
import { TeamSwitcher } from "@/registry/new-york-v4/blocks/sidebar-07/components/team-switcher"
|
||||
import {
|
||||
@@ -226,9 +225,9 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
{data.components.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={`/sink#${item.name}`}>
|
||||
<a href={`/#${item.name}`}>
|
||||
<span>{getComponentName(item.name)}</span>
|
||||
</Link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
@@ -1,63 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { registryItemFileSchema } from "shadcn/registry"
|
||||
import { z } from "zod"
|
||||
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import {
|
||||
createFileTreeForRegistryItemFiles,
|
||||
getRegistryItem,
|
||||
} from "@/lib/registry"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { BlockViewer } from "@/components/block-viewer"
|
||||
import { ComponentPreview } from "@/components/component-preview"
|
||||
|
||||
export async function BlockDisplay({ name }: { name: string }) {
|
||||
const item = await getCachedRegistryItem(name)
|
||||
|
||||
if (!item?.files) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [tree, highlightedFiles] = await Promise.all([
|
||||
getCachedFileTree(item.files),
|
||||
getCachedHighlightedFiles(item.files),
|
||||
])
|
||||
|
||||
return (
|
||||
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles}>
|
||||
<ComponentPreview
|
||||
name={item.name}
|
||||
hideCode
|
||||
className={cn(
|
||||
"my-0 **:[.preview]:h-auto **:[.preview]:p-4 **:[.preview>.p-6]:p-0",
|
||||
item.meta?.containerClassName
|
||||
)}
|
||||
/>
|
||||
</BlockViewer>
|
||||
)
|
||||
}
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
const getCachedFileTree = React.cache(
|
||||
async (files: Array<{ path: string; target?: string }>) => {
|
||||
if (!files) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await createFileTreeForRegistryItemFiles(files)
|
||||
}
|
||||
)
|
||||
|
||||
const getCachedHighlightedFiles = React.cache(
|
||||
async (files: z.infer<typeof registryItemFileSchema>[]) => {
|
||||
return await Promise.all(
|
||||
files.map(async (file) => ({
|
||||
...file,
|
||||
highlightedContent: await highlightCode(file.content ?? ""),
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -1,36 +0,0 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function BlockImage({
|
||||
name,
|
||||
width = 1440,
|
||||
height = 900,
|
||||
className,
|
||||
}: Omit<React.ComponentProps<typeof Image>, "src" | "alt"> & { name: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative aspect-[1440/900] w-full overflow-hidden rounded-lg",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={`/r/styles/new-york/${name}-light.png`}
|
||||
alt={name}
|
||||
width={width}
|
||||
height={height}
|
||||
className="object-cover dark:hidden"
|
||||
data-image="light"
|
||||
/>
|
||||
<Image
|
||||
src={`/r/styles/new-york/${name}-dark.png`}
|
||||
alt={name}
|
||||
width={width}
|
||||
height={height}
|
||||
className="hidden object-cover dark:block"
|
||||
data-image="dark"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Check,
|
||||
ChevronRight,
|
||||
Clipboard,
|
||||
File,
|
||||
Folder,
|
||||
Fullscreen,
|
||||
Monitor,
|
||||
RotateCw,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
Terminal,
|
||||
} from "lucide-react"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
import { registryItemFileSchema, registryItemSchema } from "shadcn/registry"
|
||||
import { z } from "zod"
|
||||
|
||||
import { trackEvent } from "@/lib/events"
|
||||
import { createFileTreeForRegistryItemFiles, FileTree } from "@/lib/registry"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { getIconForLanguageExtension } from "@/components/icons"
|
||||
import { OpenInV0Button } from "@/components/open-in-v0-button"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york-v4/ui/resizable"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||
|
||||
type BlockViewerContext = {
|
||||
item: z.infer<typeof registryItemSchema>
|
||||
view: "code" | "preview"
|
||||
setView: (view: "code" | "preview") => void
|
||||
activeFile: string | null
|
||||
setActiveFile: (file: string) => void
|
||||
resizablePanelRef: React.RefObject<ImperativePanelHandle | null> | null
|
||||
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null
|
||||
highlightedFiles:
|
||||
| (z.infer<typeof registryItemFileSchema> & {
|
||||
highlightedContent: string
|
||||
})[]
|
||||
| null
|
||||
iframeKey?: number
|
||||
setIframeKey?: React.Dispatch<React.SetStateAction<number>>
|
||||
}
|
||||
|
||||
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null)
|
||||
|
||||
function useBlockViewer() {
|
||||
const context = React.useContext(BlockViewerContext)
|
||||
if (!context) {
|
||||
throw new Error("useBlockViewer must be used within a BlockViewerProvider.")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
function BlockViewerProvider({
|
||||
item,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
children,
|
||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [view, setView] = React.useState<BlockViewerContext["view"]>("preview")
|
||||
const [activeFile, setActiveFile] = React.useState<
|
||||
BlockViewerContext["activeFile"]
|
||||
>(highlightedFiles?.[0].target ?? null)
|
||||
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
|
||||
const [iframeKey, setIframeKey] = React.useState(0)
|
||||
|
||||
return (
|
||||
<BlockViewerContext.Provider
|
||||
value={{
|
||||
item,
|
||||
view,
|
||||
setView,
|
||||
resizablePanelRef,
|
||||
activeFile,
|
||||
setActiveFile,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
iframeKey,
|
||||
setIframeKey,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id={item.name}
|
||||
data-view={view}
|
||||
className="group/block-view-wrapper flex min-w-0 scroll-mt-24 flex-col-reverse items-stretch gap-4 overflow-hidden md:flex-col"
|
||||
style={
|
||||
{
|
||||
"--height": item.meta?.iframeHeight ?? "930px",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</BlockViewerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerToolbar() {
|
||||
const { setView, view, item, resizablePanelRef, setIframeKey } =
|
||||
useBlockViewer()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="hidden w-full items-center gap-2 pl-2 md:pr-6 lg:flex">
|
||||
<Tabs
|
||||
value={view}
|
||||
onValueChange={(value) => setView(value as "preview" | "code")}
|
||||
>
|
||||
<TabsList className="grid h-8 grid-cols-2 items-center rounded-md p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs">
|
||||
<TabsTrigger value="preview">Preview</TabsTrigger>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Separator orientation="vertical" className="mx-2 !h-4" />
|
||||
<a
|
||||
href={`#${item.name}`}
|
||||
className="flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
|
||||
>
|
||||
{item.description?.replace(/\.$/, "")}
|
||||
</a>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<div className="h-8 items-center gap-1.5 rounded-md border p-1 shadow-none">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="100"
|
||||
onValueChange={(value) => {
|
||||
setView("preview")
|
||||
if (resizablePanelRef?.current) {
|
||||
resizablePanelRef.current.resize(parseInt(value))
|
||||
}
|
||||
}}
|
||||
className="gap-1 *:data-[slot=toggle-group-item]:!size-6 *:data-[slot=toggle-group-item]:!rounded-sm"
|
||||
>
|
||||
<ToggleGroupItem value="100" title="Desktop">
|
||||
<Monitor />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="60" title="Tablet">
|
||||
<Tablet />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="30" title="Mobile">
|
||||
<Smartphone />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" className="!h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="size-6 rounded-sm p-0"
|
||||
asChild
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<Link href={`/view/${item.name}`} target="_blank">
|
||||
<span className="sr-only">Open in New Tab</span>
|
||||
<Fullscreen />
|
||||
</Link>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="!h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="size-6 rounded-sm p-0"
|
||||
title="Refresh Preview"
|
||||
onClick={() => {
|
||||
if (setIframeKey) {
|
||||
setIframeKey((k) => k + 1)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RotateCw />
|
||||
<span className="sr-only">Refresh Preview</span>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-1 !h-4" />
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-fit gap-1 px-2 shadow-none"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
copyToClipboard(`npx shadcn@latest add ${item.name}`)
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Terminal />}
|
||||
<span>npx shadcn add {item.name}</span>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="mx-1 !h-4" />
|
||||
<OpenInV0Button name={item.name} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerIframe({ className }: { className?: string }) {
|
||||
const { item, iframeKey } = useBlockViewer()
|
||||
|
||||
return (
|
||||
<iframe
|
||||
key={iframeKey}
|
||||
src={`/view/${item.name}`}
|
||||
height={item.meta?.iframeHeight ?? 930}
|
||||
loading="lazy"
|
||||
className={cn(
|
||||
"bg-background no-scrollbar relative z-20 w-full",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerView() {
|
||||
const { resizablePanelRef } = useBlockViewer()
|
||||
|
||||
return (
|
||||
<div className="hidden group-data-[view=code]/block-view-wrapper:hidden md:h-(--height) lg:flex">
|
||||
<div className="relative grid w-full gap-4">
|
||||
<div className="absolute inset-0 right-4 [background-image:radial-gradient(#d4d4d4_1px,transparent_1px)] [background-size:20px_20px] dark:[background-image:radial-gradient(#404040_1px,transparent_1px)]"></div>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="after:bg-surface/50 relative z-10 after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl"
|
||||
>
|
||||
<ResizablePanel
|
||||
ref={resizablePanelRef}
|
||||
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
|
||||
defaultSize={100}
|
||||
minSize={30}
|
||||
>
|
||||
<BlockViewerIframe />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" />
|
||||
<ResizablePanel defaultSize={0} minSize={0} />
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerMobile({ children }: { children: React.ReactNode }) {
|
||||
const { item } = useBlockViewer()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 lg:hidden">
|
||||
<div className="flex items-center gap-2 px-2">
|
||||
<div className="line-clamp-1 text-sm font-medium">
|
||||
{item.description}
|
||||
</div>
|
||||
<div className="text-muted-foreground ml-auto shrink-0 font-mono text-xs">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
{item.meta?.mobile === "component" ? (
|
||||
children
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-xl border">
|
||||
<Image
|
||||
src={`/r/styles/new-york-v4/${item.name}-light.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="object-cover dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={`/r/styles/new-york-v4/${item.name}-dark.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="hidden object-cover dark:block"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerCode() {
|
||||
const { activeFile, highlightedFiles } = useBlockViewer()
|
||||
|
||||
const file = React.useMemo(() => {
|
||||
return highlightedFiles?.find((file) => file.target === activeFile)
|
||||
}, [highlightedFiles, activeFile])
|
||||
|
||||
if (!file) {
|
||||
return null
|
||||
}
|
||||
|
||||
const language = file.path.split(".").pop() ?? "tsx"
|
||||
|
||||
return (
|
||||
<div className="bg-code text-code-foreground mr-[14px] flex overflow-hidden rounded-xl border group-data-[view=preview]/block-view-wrapper:hidden md:h-(--height)">
|
||||
<div className="w-72">
|
||||
<BlockViewerFileTree />
|
||||
</div>
|
||||
<figure
|
||||
data-rehype-pretty-code-figure=""
|
||||
className="!mx-0 mt-0 flex min-w-0 flex-1 flex-col rounded-xl border-none"
|
||||
>
|
||||
<figcaption
|
||||
className="text-code-foreground [&_svg]:text-code-foreground flex h-12 shrink-0 items-center gap-2 border-b px-4 py-2 [&_svg]:size-4 [&_svg]:opacity-70"
|
||||
data-language={language}
|
||||
>
|
||||
{getIconForLanguageExtension(language)}
|
||||
{file.target}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<BlockCopyCodeButton />
|
||||
</div>
|
||||
</figcaption>
|
||||
<div
|
||||
key={file?.path}
|
||||
dangerouslySetInnerHTML={{ __html: file?.highlightedContent ?? "" }}
|
||||
className="no-scrollbar overflow-y-auto"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function BlockViewerFileTree() {
|
||||
const { tree } = useBlockViewer()
|
||||
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider className="flex !min-h-full flex-col border-r">
|
||||
<Sidebar collapsible="none" className="w-full flex-1">
|
||||
<SidebarGroupLabel className="h-12 rounded-none border-b px-4 text-sm">
|
||||
Files
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroup className="p-0">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="translate-x-0 gap-1.5">
|
||||
{tree.map((file, index) => (
|
||||
<Tree key={index} item={file} index={1} />
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function Tree({ item, index }: { item: FileTree; index: number }) {
|
||||
const { activeFile, setActiveFile } = useBlockViewer()
|
||||
|
||||
if (!item.children) {
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={item.path === activeFile}
|
||||
onClick={() => item.path && setActiveFile(item.path)}
|
||||
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none pl-(--index) whitespace-nowrap"
|
||||
data-index={index}
|
||||
style={
|
||||
{
|
||||
"--index": `${index * (index === 2 ? 1.2 : 1.3)}rem`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<ChevronRight className="invisible" />
|
||||
<File className="h-4 w-4" />
|
||||
{item.name}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<Collapsible
|
||||
className="group/collapsible [&[data-state=open]>button>svg:first-child]:rotate-90"
|
||||
defaultOpen
|
||||
>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
className="hover:bg-muted-foreground/15 focus:bg-muted-foreground/15 focus-visible:bg-muted-foreground/15 active:bg-muted-foreground/15 data-[active=true]:bg-muted-foreground/15 rounded-none pl-(--index) whitespace-nowrap"
|
||||
style={
|
||||
{
|
||||
"--index": `${index * (index === 1 ? 1 : 1.2)}rem`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<ChevronRight className="transition-transform" />
|
||||
<Folder />
|
||||
{item.name}
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="m-0 w-full translate-x-0 border-none p-0">
|
||||
{item.children.map((subItem, key) => (
|
||||
<Tree key={key} item={subItem} index={index + 1} />
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockCopyCodeButton() {
|
||||
const { activeFile, item } = useBlockViewer()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
const file = React.useMemo(() => {
|
||||
return item.files?.find((file) => file.target === activeFile)
|
||||
}, [activeFile, item.files])
|
||||
|
||||
const content = file?.content
|
||||
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={() => {
|
||||
copyToClipboard(content)
|
||||
trackEvent({
|
||||
name: "copy_block_code",
|
||||
properties: {
|
||||
name: item.name,
|
||||
file: file.path,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Clipboard />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewer({
|
||||
item,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
children,
|
||||
...props
|
||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<BlockViewerProvider
|
||||
item={item}
|
||||
tree={tree}
|
||||
highlightedFiles={highlightedFiles}
|
||||
{...props}
|
||||
>
|
||||
<BlockViewerToolbar />
|
||||
<BlockViewerView />
|
||||
<BlockViewerCode />
|
||||
<BlockViewerMobile>{children}</BlockViewerMobile>
|
||||
</BlockViewerProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export { BlockViewer }
|
||||
@@ -1,55 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
|
||||
import { registryCategories } from "@/registry/registry-categories"
|
||||
|
||||
export function BlocksNav() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<ScrollArea className="max-w-none">
|
||||
<div className="flex items-center">
|
||||
<BlocksNavLink
|
||||
category={{ name: "Featured", slug: "", hidden: false }}
|
||||
isActive={pathname === "/blocks"}
|
||||
/>
|
||||
{registryCategories.map((category) => (
|
||||
<BlocksNavLink
|
||||
key={category.slug}
|
||||
category={category}
|
||||
isActive={pathname === `/blocks/${category.slug}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" className="invisible" />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlocksNavLink({
|
||||
category,
|
||||
isActive,
|
||||
}: {
|
||||
category: (typeof registryCategories)[number]
|
||||
isActive: boolean
|
||||
}) {
|
||||
if (category.hidden) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blocks/${category.slug}`}
|
||||
key={category.slug}
|
||||
className="text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 items-center justify-center px-4 text-center text-base font-medium transition-colors"
|
||||
data-active={isActive}
|
||||
>
|
||||
{category.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
47
apps/v4/components/calendar-demo.tsx
Normal file
47
apps/v4/components/calendar-demo.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||
|
||||
export function CalendarDemo() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
|
||||
})
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 50),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap items-start gap-2 @md:flex-row">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-md border shadow-sm"
|
||||
/>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
|
||||
className="rounded-md border shadow-sm"
|
||||
/>
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={3}
|
||||
className="hidden rounded-md border shadow-sm @4xl:flex [&>div]:gap-5"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/registry/new-york-v4/ui/alert"
|
||||
|
||||
export function Callout({
|
||||
title,
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Alert> & { icon?: React.ReactNode }) {
|
||||
return (
|
||||
<Alert
|
||||
className={cn(
|
||||
"bg-surface text-surface-foreground mt-6 w-auto border-none md:-mx-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
{title && <AlertTitle>{title}</AlertTitle>}
|
||||
<AlertDescription className="text-card-foreground/80">
|
||||
{children}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user