mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-14 11:21:36 +00:00
Compare commits
16 Commits
shadcn@2.1
...
shadcn/aut
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eb362440a | ||
|
|
c4c5d8d419 | ||
|
|
9253682b87 | ||
|
|
c7cd16a637 | ||
|
|
eff1918d41 | ||
|
|
366d6b656b | ||
|
|
e489c5e08e | ||
|
|
d87003e0a4 | ||
|
|
a8633075f7 | ||
|
|
432d5e6e28 | ||
|
|
8f0c26f22a | ||
|
|
149b321c1b | ||
|
|
c1ae5a57cc | ||
|
|
b8ed303d8c | ||
|
|
13c97acf9f | ||
|
|
bed277c54d |
@@ -4,12 +4,12 @@ export const description = "A simple login form."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center px-4">
|
||||
<LoginForm />
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
36
apps/www/__registry__/default/block/login-02/page.tsx
Normal file
36
apps/www/__registry__/default/block/login-02/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { GalleryVerticalEnd } from "lucide-react"
|
||||
|
||||
import { LoginForm } from "@/registry/default/block/login-02/components/login-form"
|
||||
|
||||
export const description = "A two column login page with a cover image."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="grid min-h-svh lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-4 p-6 md:p-10">
|
||||
<div className="flex justify-center gap-2 md:justify-start">
|
||||
<a href="#" className="flex items-center gap-2 font-medium">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<GalleryVerticalEnd className="size-4" />
|
||||
</div>
|
||||
Acme Inc.
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<div className="w-full max-w-xs">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative hidden bg-muted lg:block">
|
||||
<img
|
||||
src="/placeholder.svg"
|
||||
alt="Image"
|
||||
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
23
apps/www/__registry__/default/block/login-03/page.tsx
Normal file
23
apps/www/__registry__/default/block/login-03/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { GalleryVerticalEnd } from "lucide-react"
|
||||
|
||||
import { LoginForm } from "@/registry/default/block/login-03/components/login-form"
|
||||
|
||||
export const description = "A login page with a muted background color."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
|
||||
<div className="flex w-full max-w-sm flex-col gap-6">
|
||||
<a href="#" className="flex items-center gap-2 self-center font-medium">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<GalleryVerticalEnd className="size-4" />
|
||||
</div>
|
||||
Acme Inc.
|
||||
</a>
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
apps/www/__registry__/default/block/login-04/page.tsx
Normal file
15
apps/www/__registry__/default/block/login-04/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { LoginForm } from "@/registry/default/block/login-04/components/login-form"
|
||||
|
||||
export const description = "A login page with form and image."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10">
|
||||
<div className="w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
apps/www/__registry__/default/block/login-05/page.tsx
Normal file
15
apps/www/__registry__/default/block/login-05/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { LoginForm } from "@/registry/default/block/login-05/components/login-form"
|
||||
|
||||
export const description = "A simple email-only login page."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -141,15 +141,16 @@ export default function Component() {
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const now = new Date()
|
||||
const referenceDate = new Date("2024-06-30")
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7
|
||||
}
|
||||
now.setDate(now.getDate() - daysToSubtract)
|
||||
return date >= now
|
||||
const startDate = new Date(referenceDate)
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||
return date >= startDate
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,12 +4,12 @@ export const description = "A simple login form."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export const containerClassName = "w-full h-full"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center px-4">
|
||||
<LoginForm />
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
36
apps/www/__registry__/new-york/block/login-02/page.tsx
Normal file
36
apps/www/__registry__/new-york/block/login-02/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { GalleryVerticalEnd } from "lucide-react"
|
||||
|
||||
import { LoginForm } from "@/registry/new-york/block/login-02/components/login-form"
|
||||
|
||||
export const description = "A two column login page with a cover image."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="grid min-h-svh lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-4 p-6 md:p-10">
|
||||
<div className="flex justify-center gap-2 md:justify-start">
|
||||
<a href="#" className="flex items-center gap-2 font-medium">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<GalleryVerticalEnd className="size-4" />
|
||||
</div>
|
||||
Acme Inc.
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<div className="w-full max-w-xs">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative hidden bg-muted lg:block">
|
||||
<img
|
||||
src="/placeholder.svg"
|
||||
alt="Image"
|
||||
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
23
apps/www/__registry__/new-york/block/login-03/page.tsx
Normal file
23
apps/www/__registry__/new-york/block/login-03/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { GalleryVerticalEnd } from "lucide-react"
|
||||
|
||||
import { LoginForm } from "@/registry/new-york/block/login-03/components/login-form"
|
||||
|
||||
export const description = "A login page with a muted background color."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
|
||||
<div className="flex w-full max-w-sm flex-col gap-6">
|
||||
<a href="#" className="flex items-center gap-2 self-center font-medium">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<GalleryVerticalEnd className="size-4" />
|
||||
</div>
|
||||
Acme Inc.
|
||||
</a>
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
apps/www/__registry__/new-york/block/login-04/page.tsx
Normal file
15
apps/www/__registry__/new-york/block/login-04/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { LoginForm } from "@/registry/new-york/block/login-04/components/login-form"
|
||||
|
||||
export const description = "A login page with form and image."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10">
|
||||
<div className="w-full max-w-sm md:max-w-3xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
apps/www/__registry__/new-york/block/login-05/page.tsx
Normal file
15
apps/www/__registry__/new-york/block/login-05/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { LoginForm } from "@/registry/new-york/block/login-05/components/login-form"
|
||||
|
||||
export const description = "A simple email-only login page."
|
||||
|
||||
export const iframeHeight = "870px"
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export default function BlocksLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Building Blocks for the Web</PageHeaderHeading>
|
||||
@@ -42,9 +42,11 @@ export default function BlocksLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="blocks" className="scroll-mt-24">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="blocks" className="scroll-mt-24">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import * as React from "react"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
|
||||
const BLOCKS_WHITELIST_PREFIXES = ["sidebar", "login"]
|
||||
|
||||
const getBlocks = unstable_cache(async () => {
|
||||
return (await getAllBlockIds()).filter((name) =>
|
||||
BLOCKS_WHITELIST_PREFIXES.some((prefix) => name.startsWith(prefix))
|
||||
)
|
||||
}, ["blocks"])
|
||||
import "@/styles/mdx.css"
|
||||
|
||||
export default async function BlocksPage() {
|
||||
const blocks = await getBlocks()
|
||||
const blocks = await getAllBlockIds()
|
||||
|
||||
return (
|
||||
<div className="gap-3 md:flex md:flex-row-reverse md:items-start">
|
||||
<div className="grid flex-1 gap-12 md:gap-24 lg:gap-48">
|
||||
{blocks.map((name, index) => (
|
||||
<React.Suspense key={`${name}-${index}`}>
|
||||
<BlockDisplay name={name} />
|
||||
</React.Suspense>
|
||||
{blocks.map((name) => (
|
||||
<BlockDisplay key={name} name={name} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function ChartsLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Beautiful Charts</PageHeaderHeading>
|
||||
@@ -38,9 +38,11 @@ export default function ChartsLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="charts" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="charts" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function ColorsLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Tailwind Colors</PageHeaderHeading>
|
||||
@@ -37,9 +37,11 @@ export default function ColorsLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="colors" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="colors" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
import { DocsPager } from "@/components/pager"
|
||||
import { DashboardTableOfContents } from "@/components/toc"
|
||||
import { badgeVariants } from "@/registry/new-york/ui/badge"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
|
||||
interface DocPageProps {
|
||||
params: {
|
||||
@@ -138,10 +137,10 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
</div>
|
||||
<div className="hidden text-sm xl:block">
|
||||
<div className="sticky top-20 -mt-6 h-[calc(100vh-3.5rem)] pt-4">
|
||||
<ScrollArea className="h-full pb-10">
|
||||
<div className="no-scrollbar h-full overflow-auto pb-10">
|
||||
{doc.toc && <DashboardTableOfContents toc={toc} />}
|
||||
<OpenInV0Cta className="mt-6 max-w-[80%]" />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -8,15 +8,13 @@ interface DocsLayoutProps {
|
||||
|
||||
export default function DocsLayout({ children }: DocsLayoutProps) {
|
||||
return (
|
||||
<div className="border-b border-border/40 dark:border-border">
|
||||
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
|
||||
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
|
||||
<DocsSidebarNav config={docsConfig} />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 border-r border-border/40 dark:border-border md:sticky md:block">
|
||||
<div className="no-scrollbar h-full overflow-auto py-6 pr-6 lg:py-8">
|
||||
<DocsSidebarNav config={docsConfig} />
|
||||
</div>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
@@ -10,7 +9,7 @@ import {
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { Button, buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Examples",
|
||||
@@ -23,7 +22,7 @@ interface ExamplesLayoutProps {
|
||||
|
||||
export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
@@ -43,12 +42,14 @@ export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section>
|
||||
<ExamplesNav />
|
||||
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section>
|
||||
<ExamplesNav />
|
||||
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ interface AppLayoutProps {
|
||||
|
||||
export default function AppLayout({ children }: AppLayoutProps) {
|
||||
return (
|
||||
<div className="mx-auto w-full border-border/40 dark:border-border min-[1800px]:max-w-[1536px] min-[1800px]:border-x">
|
||||
<SiteHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<SiteFooter />
|
||||
<div data-wrapper="" className="border-border/40 dark:border-border">
|
||||
<div className="mx-auto w-full border-border/40 dark:border-border min-[1800px]:max-w-[1536px] min-[1800px]:border-x">
|
||||
<SiteHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export default function IndexPage() {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Build your component library</PageHeaderHeading>
|
||||
@@ -38,26 +38,28 @@ export default function IndexPage() {
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary" />
|
||||
<section className="overflow-hidden rounded-lg border bg-background shadow-md md:hidden md:shadow-xl">
|
||||
<Image
|
||||
src="/examples/cards-light.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/cards-dark.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</section>
|
||||
<section className="hidden md:block [&>div]:p-0">
|
||||
<CardsNewYork />
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary" />
|
||||
<section className="overflow-hidden rounded-lg border bg-background shadow-md md:hidden md:shadow-xl">
|
||||
<Image
|
||||
src="/examples/cards-light.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/cards-dark.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</section>
|
||||
<section className="hidden md:block [&>div]:p-0">
|
||||
<CardsNewYork />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
52
apps/www/app/(app)/themes/layout.tsx
Normal file
52
apps/www/app/(app)/themes/layout.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { ThemeCustomizer } from "@/components/theme-customizer"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Themes",
|
||||
description: "Hand-picked themes that you can copy and paste into your apps.",
|
||||
}
|
||||
|
||||
export default function ThemesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<ThemeWrapper
|
||||
defaultTheme="zinc"
|
||||
className="relative flex w-full flex-col items-start md:flex-row"
|
||||
>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<ThemeCustomizer />
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
</ThemeWrapper>
|
||||
<div className="container py-6">
|
||||
<section id="themes" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +1,7 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import "public/registry/themes.css"
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { ThemeCustomizer } from "@/components/theme-customizer"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import { ThemesTabs } from "@/app/(app)/themes/tabs"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Themes",
|
||||
description: "Hand-picked themes that you can copy and paste into your apps.",
|
||||
}
|
||||
import "public/registry/themes.css"
|
||||
|
||||
export default function ThemesPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<ThemeWrapper
|
||||
defaultTheme="zinc"
|
||||
className="relative flex w-full flex-col items-start md:flex-row"
|
||||
>
|
||||
<PageHeader className="w-full">
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<ThemeCustomizer />
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
</ThemeWrapper>
|
||||
<ThemesTabs />
|
||||
</div>
|
||||
)
|
||||
return <ThemesTabs />
|
||||
}
|
||||
|
||||
@@ -1,73 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import CardsDefault from "@/registry/default/example/cards"
|
||||
import { Skeleton } from "@/registry/default/ui/skeleton"
|
||||
import CardsNewYork from "@/registry/new-york/example/cards"
|
||||
|
||||
export function ThemesTabs() {
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
const [config] = useConfig()
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{!mounted ? (
|
||||
<div className="md:grids-col-2 grid md:gap-4 lg:grid-cols-10 xl:gap-6">
|
||||
<div className="space-y-4 lg:col-span-4 xl:col-span-6 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="grid gap-1 sm:grid-cols-[260px_1fr] md:hidden">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="pt-3 sm:pl-2 sm:pt-0 xl:pl-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="pt-3 sm:col-span-2 xl:pt-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
|
||||
<div className="space-y-4 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="space-y-4 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="hidden xl:block">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 lg:col-span-6 xl:col-span-4 xl:space-y-6">
|
||||
<div className="hidden gap-1 sm:grid-cols-[260px_1fr] md:grid">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="pt-3 sm:pl-2 sm:pt-0 xl:pl-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="pt-3 sm:col-span-2 xl:pt-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ThemeWrapper>
|
||||
{config.style === "new-york" && <CardsNewYork />}
|
||||
{config.style === "default" && <CardsDefault />}
|
||||
</ThemeWrapper>
|
||||
)}
|
||||
<ThemeWrapper>
|
||||
<CardsNewYork />
|
||||
</ThemeWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import * as React from "react"
|
||||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { getAllBlockIds, getBlock } from "@/lib/blocks"
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
import { BlockChunk } from "@/components/block-chunk"
|
||||
import { BlockWrapper } from "@/components/block-wrapper"
|
||||
import { Style, styles } from "@/registry/registry-styles"
|
||||
|
||||
import "@/styles/mdx.css"
|
||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||
|
||||
const getCachedRegistryItem = React.cache(
|
||||
async (name: string, style: Style["name"]) => {
|
||||
return await getRegistryItem(name, style)
|
||||
}
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -19,23 +25,23 @@ export async function generateMetadata({
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const { name, style } = params
|
||||
const block = await getBlock(name, style)
|
||||
const item = await getCachedRegistryItem(name, style)
|
||||
|
||||
if (!block) {
|
||||
if (!item) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const title = block.name
|
||||
const description = block.description
|
||||
const title = item.name
|
||||
const description = item.description
|
||||
|
||||
return {
|
||||
title: `${block.description} - ${block.name}`,
|
||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "article",
|
||||
url: absoluteUrl(`/blocks/${block.name}`),
|
||||
url: absoluteUrl(`/blocks/${style}/${item.name}`),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
@@ -76,39 +82,22 @@ export default async function BlockPage({
|
||||
}
|
||||
}) {
|
||||
const { name, style } = params
|
||||
const block = await getBlock(name, style)
|
||||
const item = await getCachedRegistryItem(name, style)
|
||||
const Component = getRegistryComponent(name, style)
|
||||
|
||||
if (!block) {
|
||||
if (!item || !Component) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const Component = block.component
|
||||
|
||||
const chunks = block.chunks?.map((chunk) => ({ ...chunk }))
|
||||
delete block.component
|
||||
block.chunks?.map((chunk) => delete chunk.component)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ThemesStyle /> */}
|
||||
<div
|
||||
className={cn(
|
||||
"themes-wrapper bg-background",
|
||||
block.container?.className
|
||||
item.meta?.containerClassName
|
||||
)}
|
||||
>
|
||||
<BlockWrapper block={block}>
|
||||
<Component />
|
||||
{chunks?.map((chunk, index) => (
|
||||
<BlockChunk
|
||||
key={chunk.name}
|
||||
block={block}
|
||||
chunk={block.chunks?.[index]}
|
||||
>
|
||||
<chunk.component />
|
||||
</BlockChunk>
|
||||
))}
|
||||
</BlockWrapper>
|
||||
<Component />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import "@/styles/globals.css"
|
||||
import { Metadata, Viewport } from "next"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { fontSans } from "@/lib/fonts"
|
||||
import { META_THEME_COLORS, siteConfig } from "@/config/site"
|
||||
import { fontMono, fontSans } from "@/lib/fonts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { ThemeProvider } from "@/components/providers"
|
||||
@@ -65,10 +65,7 @@ export const metadata: Metadata = {
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: "(prefers-color-scheme: light)", color: "white" },
|
||||
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
||||
],
|
||||
themeColor: META_THEME_COLORS.light,
|
||||
}
|
||||
|
||||
interface RootLayoutProps {
|
||||
@@ -79,11 +76,24 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head />
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
try {
|
||||
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}')
|
||||
}
|
||||
} catch (_) {}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={cn(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
fontSans.variable
|
||||
"min-h-svh bg-background font-sans antialiased",
|
||||
fontSans.variable,
|
||||
fontMono.variable
|
||||
)}
|
||||
>
|
||||
<ThemeProvider
|
||||
@@ -91,9 +101,10 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme
|
||||
>
|
||||
<div vaul-drawer-wrapper="">
|
||||
<div className="relative flex min-h-screen flex-col bg-background">
|
||||
<div className="relative flex min-h-svh flex-col bg-background">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import Link from "next/link"
|
||||
import { ArrowRight, PanelLeft } from "lucide-react"
|
||||
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
export function Announcement() {
|
||||
return (
|
||||
<Link
|
||||
href="/docs/components/sidebar"
|
||||
className="group inline-flex items-center px-0.5 text-sm font-medium"
|
||||
className="group mb-2 inline-flex items-center px-0.5 text-sm font-medium"
|
||||
>
|
||||
<PanelLeft className="h-4 w-4" />{" "}
|
||||
<Separator className="mx-2 h-4" orientation="vertical" />{" "}
|
||||
<span className="underline-offset-4 group-hover:underline">
|
||||
New sidebar component
|
||||
</span>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useLiftMode } from "@/hooks/use-lift-mode"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Block, type BlockChunk } from "@/registry/schema"
|
||||
|
||||
export function BlockChunk({
|
||||
block,
|
||||
chunk,
|
||||
children,
|
||||
...props
|
||||
}: React.PropsWithChildren<{ block: Block; chunk?: BlockChunk }>) {
|
||||
const { isLiftMode } = useLiftMode(block.name)
|
||||
|
||||
if (!chunk) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isLiftMode && (
|
||||
<motion.div
|
||||
key={chunk.name}
|
||||
x-chunk-container={chunk.name}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0, transition: { ease: "easeOut", duration: 0.2 } }}
|
||||
transition={{ ease: "easeIn", duration: 0.2 }}
|
||||
className={cn(
|
||||
"group rounded-xl bg-background shadow-xl transition",
|
||||
chunk.container?.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative z-30">{children}</div>
|
||||
{chunk.code && (
|
||||
<div className="absolute inset-x-0 top-0 z-30 flex px-4 py-3 opacity-0 transition-all duration-200 ease-in group-hover:-translate-y-12 group-hover:opacity-100">
|
||||
<div className="flex w-full items-center justify-end gap-2">
|
||||
<BlockCopyButton
|
||||
event="copy_chunk_code"
|
||||
name={chunk.name}
|
||||
code={chunk.code}
|
||||
/>
|
||||
<V0Button size="icon" name={chunk.name} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +1,52 @@
|
||||
import { unstable_cache } from "next/cache"
|
||||
import * as React from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getBlock } from "@/lib/blocks"
|
||||
import { BlockPreview } from "@/components/block-preview"
|
||||
|
||||
const getBlockByName = unstable_cache(
|
||||
async (name: string) => {
|
||||
const block = await getBlock(name)
|
||||
|
||||
if (!block) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
name: block.name,
|
||||
style: block.style,
|
||||
description: block.description,
|
||||
container: block.container,
|
||||
}
|
||||
},
|
||||
["block"]
|
||||
)
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import {
|
||||
createFileTreeForRegistryItemFiles,
|
||||
getRegistryItem,
|
||||
} from "@/lib/registry"
|
||||
import { BlockViewer } from "@/components/block-viewer"
|
||||
import { registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
export async function BlockDisplay({ name }: { name: string }) {
|
||||
const block = await getBlockByName(name)
|
||||
const item = await getCachedRegistryItem(name)
|
||||
|
||||
if (!block) {
|
||||
if (!item?.files) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <BlockPreview key={block.name} block={block} />
|
||||
const [tree, highlightedFiles] = await Promise.all([
|
||||
getCachedFileTree(item.files),
|
||||
getCachedHighlightedFiles(item.files),
|
||||
])
|
||||
|
||||
return (
|
||||
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles} />
|
||||
)
|
||||
}
|
||||
|
||||
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,67 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
|
||||
import { BlockToolbar } from "@/components/block-toolbar"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york/ui/resizable"
|
||||
import { type Block } from "@/registry/schema"
|
||||
|
||||
export function BlockPreview({
|
||||
block,
|
||||
}: {
|
||||
block: Pick<Block, "name" | "style" | "description" | "container">
|
||||
}) {
|
||||
const ref = React.useRef<ImperativePanelHandle>(null)
|
||||
|
||||
return (
|
||||
<div
|
||||
id={block.name}
|
||||
className="relative grid w-full scroll-m-20 gap-4"
|
||||
style={
|
||||
{
|
||||
"--container-height": block.container?.height,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<BlockToolbar block={block} resizablePanelRef={ref} />
|
||||
<ResizablePanelGroup direction="horizontal" className="relative z-10">
|
||||
<ResizablePanel
|
||||
ref={ref}
|
||||
className="relative aspect-[4/2.5] rounded-lg border bg-background md:aspect-auto"
|
||||
defaultSize={100}
|
||||
minSize={30}
|
||||
>
|
||||
<Image
|
||||
src={`/images/blocks/${block.name}.png`}
|
||||
alt={block.name}
|
||||
data-block={block.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:hidden sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={`/images/blocks/${block.name}-dark.png`}
|
||||
alt={block.name}
|
||||
data-block={block.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<iframe
|
||||
src={`/blocks/${block.style}/${block.name}`}
|
||||
height={block.container?.height ?? 450}
|
||||
className="chunk-mode relative z-20 hidden w-full bg-background md:block"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 sm:block" />
|
||||
<ResizablePanel defaultSize={0} minSize={0} />
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Check,
|
||||
Fullscreen,
|
||||
Monitor,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
Terminal,
|
||||
} from "lucide-react"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york/ui/toggle-group"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function BlockToolbar({
|
||||
block,
|
||||
resizablePanelRef,
|
||||
}: {
|
||||
block: Pick<Block, "name" | "style" | "description" | "container">
|
||||
resizablePanelRef: React.RefObject<ImperativePanelHandle>
|
||||
}) {
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<Button asChild variant="link" className="whitespace-normal px-1 md:px-2">
|
||||
<a href={`#${block.name}`}>{block.description}</a>
|
||||
</Button>
|
||||
<div className="ml-auto hidden items-center gap-2 md:flex md:pr-[14px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 rounded-md border bg-muted shadow-none"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
copyToClipboard(`npx shadcn@latest add ${block.name}`)
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Terminal />}
|
||||
npx shadcn add {block.name}
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<div className="hidden h-7 items-center gap-1.5 rounded-md border p-[2px] shadow-none md:flex">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="100"
|
||||
onValueChange={(value) => {
|
||||
if (resizablePanelRef.current) {
|
||||
resizablePanelRef.current.resize(parseInt(value))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value="100"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Desktop"
|
||||
>
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="60"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Tablet"
|
||||
>
|
||||
<Tablet className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="30"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Mobile"
|
||||
>
|
||||
<Smartphone className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
asChild
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<Link
|
||||
href={`/blocks/${block.style}/${block.name}`}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="sr-only">Open in New Tab</span>
|
||||
<Fullscreen className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<V0Button
|
||||
className="hidden shadow-none md:flex"
|
||||
id={`v0-button-${block.name}`}
|
||||
name={`v0-${block.name}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
452
apps/www/components/block-viewer.tsx
Normal file
452
apps/www/components/block-viewer.tsx
Normal file
@@ -0,0 +1,452 @@
|
||||
"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,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
Terminal,
|
||||
} from "lucide-react"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
import { z } from "zod"
|
||||
|
||||
import { trackEvent } from "@/lib/events"
|
||||
import { FileTree, createFileTreeForRegistryItemFiles } from "@/lib/registry"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york/ui/collapsible"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york/ui/resizable"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york/ui/sidebar"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york/ui/tabs"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york/ui/toggle-group"
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { registryEntrySchema, registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
type BlockViewerContext = {
|
||||
item: z.infer<typeof registryEntrySchema>
|
||||
view: "code" | "preview"
|
||||
setView: (view: "code" | "preview") => void
|
||||
style?: Style["name"]
|
||||
setStyle: (style: Style["name"]) => void
|
||||
activeFile: string | null
|
||||
setActiveFile: (file: string) => void
|
||||
resizablePanelRef: React.RefObject<ImperativePanelHandle> | null
|
||||
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null
|
||||
highlightedFiles:
|
||||
| (z.infer<typeof registryItemFileSchema> & {
|
||||
highlightedContent: string
|
||||
})[]
|
||||
| null
|
||||
}
|
||||
|
||||
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 [style, setStyle] =
|
||||
React.useState<BlockViewerContext["style"]>("new-york")
|
||||
const [activeFile, setActiveFile] = React.useState<
|
||||
BlockViewerContext["activeFile"]
|
||||
>(highlightedFiles?.[0].target ?? null)
|
||||
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
|
||||
|
||||
return (
|
||||
<BlockViewerContext.Provider
|
||||
value={{
|
||||
item,
|
||||
view,
|
||||
setView,
|
||||
style,
|
||||
setStyle,
|
||||
resizablePanelRef,
|
||||
activeFile,
|
||||
setActiveFile,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id={item.name}
|
||||
data-view={view}
|
||||
className="group/block-view-wrapper flex min-w-0 flex-col items-stretch gap-4"
|
||||
style={
|
||||
{
|
||||
"--height": item.meta?.iframeHeight ?? 450,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</BlockViewerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerToolbar() {
|
||||
const { setView, item, resizablePanelRef, style } = useBlockViewer()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center gap-2 md:pr-[14px]">
|
||||
<Tabs
|
||||
defaultValue="preview"
|
||||
onValueChange={(value) => setView(value as "preview" | "code")}
|
||||
className="hidden lg:flex"
|
||||
>
|
||||
<TabsList className="h-7 items-center rounded-md p-0 px-[calc(theme(spacing.1)_-_2px)] py-[theme(spacing.1)]">
|
||||
<TabsTrigger
|
||||
value="preview"
|
||||
className="h-[1.45rem] rounded-sm px-2 text-xs"
|
||||
>
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="code"
|
||||
className="h-[1.45rem] rounded-sm px-2 text-xs"
|
||||
>
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 lg:flex" />
|
||||
<a
|
||||
href={`#${item.name}`}
|
||||
className="text-sm font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
{item.description}
|
||||
</a>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden h-7 w-7 rounded-md border bg-transparent shadow-none md:flex lg:w-auto"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
copyToClipboard(`npx shadcn@latest add ${name}`)
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Terminal />}
|
||||
<span className="hidden lg:inline">npx shadcn add {item.name}</span>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<div className="hidden h-7 items-center gap-1.5 rounded-md border p-[2px] shadow-none lg:flex">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="100"
|
||||
onValueChange={(value) => {
|
||||
if (resizablePanelRef?.current) {
|
||||
resizablePanelRef.current.resize(parseInt(value))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value="100"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Desktop"
|
||||
>
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="60"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Tablet"
|
||||
>
|
||||
<Tablet className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="30"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Mobile"
|
||||
>
|
||||
<Smartphone className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
asChild
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<Link href={`/blocks/${style}/${item.name}`} target="_blank">
|
||||
<span className="sr-only">Open in New Tab</span>
|
||||
<Fullscreen className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 xl:flex" />
|
||||
<V0Button
|
||||
className="hidden shadow-none sm:flex"
|
||||
id={`v0-button-${item.name}`}
|
||||
name={`v0-${item.name}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerView() {
|
||||
const { item, style, resizablePanelRef } = useBlockViewer()
|
||||
|
||||
return (
|
||||
<div className="h-[--height] group-data-[view=code]/block-view-wrapper:hidden">
|
||||
<div className="grid w-full gap-4">
|
||||
<ResizablePanelGroup direction="horizontal" className="relative z-10">
|
||||
<ResizablePanel
|
||||
ref={resizablePanelRef}
|
||||
className="relative aspect-[4/2.5] rounded-xl border bg-background md:aspect-auto"
|
||||
defaultSize={100}
|
||||
minSize={30}
|
||||
>
|
||||
<Image
|
||||
src={`/images/blocks/${item.name}.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:hidden sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={`/images/blocks/${item.name}-dark.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<iframe
|
||||
src={`/blocks/${style}/${item.name}`}
|
||||
height={item.meta?.iframeHeight ?? 450}
|
||||
className="chunk-mode relative z-20 hidden w-full bg-background md:block"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 md:block" />
|
||||
<ResizablePanel defaultSize={0} minSize={0} />
|
||||
</ResizablePanelGroup>
|
||||
</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
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mr-[14px] flex h-[--height] overflow-hidden rounded-xl bg-zinc-950 text-white group-data-[view=preview]/block-view-wrapper:hidden">
|
||||
<div className="w-[280px]">
|
||||
<BlockViewerFileTree />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="flex h-12 items-center gap-2 border-b border-zinc-700 bg-zinc-900 px-4 text-sm font-medium">
|
||||
<File className="size-4" />
|
||||
{file.target}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<BlockCopyCodeButton />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
key={file?.path}
|
||||
data-rehype-pretty-code-fragment
|
||||
dangerouslySetInnerHTML={{ __html: file?.highlightedContent ?? "" }}
|
||||
className="relative flex-1 overflow-hidden after:absolute after:inset-y-0 after:left-0 after:w-10 after:bg-zinc-950 [&_.line:before]:sticky [&_.line:before]:left-2 [&_.line:before]:z-10 [&_.line:before]:translate-y-[-1px] [&_.line:before]:pr-1 [&_pre]:h-[--height] [&_pre]:overflow-auto [&_pre]:!bg-transparent [&_pre]:pb-20 [&_pre]:pt-4 [&_pre]:font-mono [&_pre]:text-sm [&_pre]:leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function BlockViewerFileTree() {
|
||||
const { tree } = useBlockViewer()
|
||||
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider className="flex !min-h-full flex-col">
|
||||
<Sidebar
|
||||
collapsible="none"
|
||||
className="w-full flex-1 border-r border-zinc-700 bg-zinc-900 text-white"
|
||||
>
|
||||
<SidebarGroupLabel className="h-12 rounded-none border-b border-zinc-700 px-4 text-sm text-white">
|
||||
Files
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroup className="p-0">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="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="whitespace-nowrap rounded-none pl-[--index] hover:bg-zinc-700 hover:text-white focus:bg-zinc-700 focus:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white"
|
||||
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="whitespace-nowrap rounded-none pl-[--index] hover:bg-zinc-700 hover:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white data-[state=open]:hover:bg-zinc-700 data-[state=open]:hover:text-white"
|
||||
style={
|
||||
{
|
||||
"--index": `${index * (index === 1 ? 1 : 1.2)}rem`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4 transition-transform" />
|
||||
<Folder className="h-4 w-4" />
|
||||
{item.name}
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="m-0 w-full 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
|
||||
onClick={() => {
|
||||
copyToClipboard(content)
|
||||
trackEvent({
|
||||
name: "copy_block_code",
|
||||
properties: {
|
||||
name: item.name,
|
||||
file: file.path,
|
||||
},
|
||||
})
|
||||
}}
|
||||
className="h-7 w-7 shrink-0 rounded-lg p-0 hover:bg-zinc-700 hover:text-white focus:bg-zinc-700 focus:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white [&>svg]:size-3"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? <Check /> : <Clipboard />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewer({
|
||||
item,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
...props
|
||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles">) {
|
||||
return (
|
||||
<BlockViewerProvider
|
||||
item={item}
|
||||
tree={tree}
|
||||
highlightedFiles={highlightedFiles}
|
||||
{...props}
|
||||
>
|
||||
<BlockViewerToolbar />
|
||||
<BlockViewerView />
|
||||
<BlockViewerCode />
|
||||
</BlockViewerProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export { BlockViewer }
|
||||
@@ -1,69 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
|
||||
import { useLiftMode } from "@/hooks/use-lift-mode"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function BlockWrapper({
|
||||
block,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ block: Block }>) {
|
||||
const { isLiftMode } = useLiftMode(block.name)
|
||||
|
||||
React.useEffect(() => {
|
||||
const components = document.querySelectorAll("[x-chunk]")
|
||||
block.chunks?.map((chunk, index) => {
|
||||
const $chunk = document.querySelector<HTMLElement>(
|
||||
`[x-chunk="${chunk.name}"]`
|
||||
)
|
||||
const $wrapper = document.querySelector<HTMLElement>(
|
||||
`[x-chunk-container="${chunk.name}"]`
|
||||
)
|
||||
|
||||
const $component = components[index]
|
||||
|
||||
if (!$chunk || !$component) {
|
||||
return
|
||||
}
|
||||
|
||||
const position = $component.getBoundingClientRect()
|
||||
$chunk.style.zIndex = "40"
|
||||
// $chunk.style.position = "absolute"
|
||||
// $chunk.style.top = `${position.top}px`
|
||||
// $chunk.style.left = `${position.left}px`
|
||||
$chunk.style.width = `${position.width}px`
|
||||
$chunk.style.height = `${position.height}px`
|
||||
|
||||
if ($wrapper) {
|
||||
$wrapper.style.zIndex = "40"
|
||||
$wrapper.style.position = "absolute"
|
||||
$wrapper.style.top = `${position.top}px`
|
||||
$wrapper.style.left = `${position.left}px`
|
||||
$wrapper.style.width = `${position.width}px`
|
||||
$wrapper.style.height = `${position.height}px`
|
||||
}
|
||||
})
|
||||
}, [block.chunks, isLiftMode])
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<AnimatePresence>
|
||||
{isLiftMode && (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-30 bg-background/90 fill-mode-backwards"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { ease: "easeOut", duration: 0.38 },
|
||||
}}
|
||||
transition={{ ease: "easeOut", duration: 0.2, delay: 0.18 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,8 @@ import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import { useThemesConfig } from "@/hooks/use-themes-config"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { ChartCopyButton } from "@/components/chart-copy-button"
|
||||
import { Chart } from "@/components/chart-display"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -18,13 +19,14 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york/ui/tabs"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function ChartCodeViewer({
|
||||
chart,
|
||||
className,
|
||||
children,
|
||||
}: { chart: Block } & React.ComponentProps<"div">) {
|
||||
}: {
|
||||
chart: Chart
|
||||
} & React.ComponentProps<"div">) {
|
||||
const [tab, setTab] = React.useState("code")
|
||||
const { themesConfig } = useThemesConfig()
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)")
|
||||
@@ -85,10 +87,10 @@ ${Object.entries(themesConfig?.activeTheme.cssVars.dark || {})
|
||||
</TabsList>
|
||||
{tab === "code" && (
|
||||
<div className="ml-auto flex items-center justify-center gap-2">
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_code"
|
||||
name={chart.name}
|
||||
code={chart.code}
|
||||
code={chart.files?.[0]?.content ?? ""}
|
||||
/>
|
||||
<V0Button
|
||||
id={`v0-button-${chart.name}`}
|
||||
@@ -98,7 +100,7 @@ ${Object.entries(themesConfig?.activeTheme.cssVars.dark || {})
|
||||
</div>
|
||||
)}
|
||||
{tab === "theme" && (
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_theme"
|
||||
name={chart.name}
|
||||
code={themeCode}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/registry/new-york/ui/tooltip"
|
||||
|
||||
export function BlockCopyButton({
|
||||
export function ChartCopyButton({
|
||||
event,
|
||||
name,
|
||||
code,
|
||||
@@ -1,19 +1,27 @@
|
||||
import { getBlock } from "@/lib/blocks"
|
||||
import * as React from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import { getRegistryItem } from "@/lib/registry"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ChartToolbar } from "@/components/chart-toolbar"
|
||||
import { registryEntrySchema } from "@/registry/schema"
|
||||
|
||||
export type Chart = z.infer<typeof registryEntrySchema> & {
|
||||
highlightedCode: string
|
||||
}
|
||||
|
||||
export async function ChartDisplay({
|
||||
name,
|
||||
children,
|
||||
className,
|
||||
}: { name: string } & React.ComponentProps<"div">) {
|
||||
const chart = await getBlock(name)
|
||||
const chart = await getCachedRegistryItem(name)
|
||||
const highlightedCode = await getChartHighlightedCode(
|
||||
chart?.files?.[0]?.content ?? ""
|
||||
)
|
||||
|
||||
// Cannot (and don't need to) pass to the client.
|
||||
delete chart?.component
|
||||
delete chart?.chunks
|
||||
|
||||
if (!chart) {
|
||||
if (!chart || !highlightedCode) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -25,7 +33,7 @@ export async function ChartDisplay({
|
||||
)}
|
||||
>
|
||||
<ChartToolbar
|
||||
chart={chart}
|
||||
chart={{ ...chart, highlightedCode }}
|
||||
className="relative z-20 flex justify-end border-b bg-card px-3 py-2.5 text-card-foreground"
|
||||
>
|
||||
{children}
|
||||
@@ -36,3 +44,11 @@ export async function ChartDisplay({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
const getChartHighlightedCode = React.cache(async (content: string) => {
|
||||
return await highlightCode(content)
|
||||
})
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { ChartCodeViewer } from "@/components/chart-code-viewer"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
import "@/styles/mdx.css"
|
||||
import {
|
||||
@@ -17,21 +15,26 @@ import {
|
||||
Radar,
|
||||
} from "lucide-react"
|
||||
|
||||
import { ChartCopyButton } from "@/components/chart-copy-button"
|
||||
import { Chart } from "@/components/chart-display"
|
||||
|
||||
export function ChartToolbar({
|
||||
chart,
|
||||
className,
|
||||
children,
|
||||
}: { chart: Block } & React.ComponentProps<"div">) {
|
||||
}: {
|
||||
chart: Chart
|
||||
} & React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<div className="flex items-center gap-1.5 pl-1 text-[13px] text-muted-foreground [&>svg]:h-[0.9rem] [&>svg]:w-[0.9rem]">
|
||||
<ChartTitle chart={chart} />
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 [&>form]:flex">
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_code"
|
||||
name={chart.name}
|
||||
code={chart.code}
|
||||
code={chart.files?.[0]?.content ?? ""}
|
||||
className="[&_svg]-h-3 h-6 w-6 rounded-[6px] bg-transparent text-foreground shadow-none hover:bg-muted dark:text-foreground [&_svg]:w-3"
|
||||
/>
|
||||
<Separator orientation="vertical" className="mx-0 hidden h-4 md:flex" />
|
||||
@@ -41,7 +44,7 @@ export function ChartToolbar({
|
||||
)
|
||||
}
|
||||
|
||||
function ChartTitle({ chart }: { chart: Block }) {
|
||||
function ChartTitle({ chart }: { chart: Chart }) {
|
||||
const { subcategory } = chart
|
||||
|
||||
if (!subcategory) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { type Color } from "@/lib/colors"
|
||||
import { trackEvent } from "@/lib/events"
|
||||
import { useColors } from "@/hooks/use-colors"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||
|
||||
export function Color({ color }: { color: Color }) {
|
||||
const { format } = useColors()
|
||||
|
||||
@@ -55,7 +55,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"relative h-8 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64"
|
||||
"relative h-8 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-56 xl:w-64"
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
{...props}
|
||||
|
||||
@@ -94,10 +94,7 @@ export function ComponentPreview({
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<div className="absolute inset-0 hidden w-[1600px] bg-background md:block">
|
||||
<iframe
|
||||
src={`/blocks/${config.style}/${name}`}
|
||||
className="size-full"
|
||||
/>
|
||||
<iframe src={`/blocks/new-york/${name}`} className="size-full" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ export function MainNav() {
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</Link>
|
||||
<nav className="flex items-center gap-4 text-sm lg:gap-6">
|
||||
<nav className="flex items-center gap-4 text-sm xl:gap-6">
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(
|
||||
@@ -77,7 +77,7 @@ export function MainNav() {
|
||||
<Link
|
||||
href="/examples"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
"hidden transition-colors hover:text-foreground/80 lg:inline-block",
|
||||
pathname?.startsWith("/examples")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
|
||||
@@ -3,22 +3,32 @@
|
||||
import * as React from "react"
|
||||
import Link, { LinkProps } from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { MenuIcon } from "lucide-react"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { useMetaColor } from "@/hooks/use-meta-color"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/new-york/ui/sheet"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/new-york/ui/drawer"
|
||||
|
||||
export function MobileNav() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const { setMetaColor, metaColor } = useMetaColor()
|
||||
|
||||
const onOpenChange = React.useCallback(
|
||||
(open: boolean) => {
|
||||
setOpen(open)
|
||||
setMetaColor(open ? "#09090b" : metaColor)
|
||||
},
|
||||
[setMetaColor, metaColor]
|
||||
)
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="-ml-2 mr-2 h-8 w-8 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
|
||||
@@ -29,7 +39,7 @@ export function MobileNav() {
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="size-6"
|
||||
className="!size-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -39,17 +49,9 @@ export function MobileNav() {
|
||||
</svg>
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="pr-0">
|
||||
<MobileLink
|
||||
href="/"
|
||||
className="flex items-center"
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<Icons.logo className="mr-2 h-4 w-4" />
|
||||
<span className="font-bold">{siteConfig.name}</span>
|
||||
</MobileLink>
|
||||
<ScrollArea className="my-4 h-[calc(100vh-8rem)] pb-10 pl-6">
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[60svh] p-0">
|
||||
<div className="overflow-auto p-6">
|
||||
<div className="flex flex-col space-y-3">
|
||||
{docsConfig.mainNav?.map(
|
||||
(item) =>
|
||||
@@ -93,9 +95,9 @@ export function MobileNav() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ function MobileLink({
|
||||
router.push(href.toString())
|
||||
onOpenChange?.(false)
|
||||
}}
|
||||
className={cn(className)}
|
||||
className={cn("text-base", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
35
apps/www/components/mode-switcher.tsx
Normal file
35
apps/www/components/mode-switcher.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { META_THEME_COLORS } from "@/config/site"
|
||||
import { useMetaColor } from "@/hooks/use-meta-color"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export function ModeSwitcher() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
const { setMetaColor } = useMetaColor()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
setMetaColor(
|
||||
resolvedTheme === "dark"
|
||||
? META_THEME_COLORS.light
|
||||
: META_THEME_COLORS.dark
|
||||
)
|
||||
}, [resolvedTheme, setTheme, setMetaColor])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="group/toggle h-8 w-8 px-0"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -8,12 +8,12 @@ function PageHeader({
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"mx-auto flex flex-col items-start gap-2 px-4 py-8 md:py-12 md:pb-8 lg:py-12 lg:pb-10",
|
||||
"flex flex-col items-start gap-2 border-b border-border/40 py-8 dark:border-border md:py-10 lg:py-12",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<div className="container">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
import * as React from "react"
|
||||
import { Provider as JotaiProvider } from "jotai"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
import { TooltipProvider } from "@/registry/new-york/ui/tooltip"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||
return (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
|
||||
@@ -44,18 +44,16 @@ export function DocsSidebarNavItems({
|
||||
pathname,
|
||||
}: DocsSidebarNavItemsProps) {
|
||||
return items?.length ? (
|
||||
<div className="grid grid-flow-row auto-rows-max text-sm">
|
||||
<div className="grid grid-flow-row auto-rows-max gap-0.5 text-sm">
|
||||
{items.map((item, index) =>
|
||||
item.href && !item.disabled ? (
|
||||
<Link
|
||||
key={index}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline",
|
||||
"group flex w-full items-center px-2 py-1 font-normal text-foreground underline-offset-2 hover:underline",
|
||||
item.disabled && "cursor-not-allowed opacity-60",
|
||||
pathname === item.href
|
||||
? "font-medium text-foreground"
|
||||
: "text-muted-foreground"
|
||||
pathname === item.href && "underline"
|
||||
)}
|
||||
target={item.external ? "_blank" : ""}
|
||||
rel={item.external ? "noreferrer" : ""}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { siteConfig } from "@/config/site"
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="py-6 md:px-8 md:py-0">
|
||||
<footer className="border-t border-border/40 py-6 dark:border-border md:px-8 md:py-0">
|
||||
<div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<p className="text-balance text-center text-sm leading-loose text-muted-foreground md:text-left">
|
||||
Built by{" "}
|
||||
|
||||
@@ -1,60 +1,35 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CommandMenu } from "@/components/command-menu"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { MainNav } from "@/components/main-nav"
|
||||
import { MobileNav } from "@/components/mobile-nav"
|
||||
import { ModeToggle } from "@/components/mode-toggle"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 dark:border-border">
|
||||
<div className="container flex h-14 max-w-screen-2xl items-center">
|
||||
<div className="flex h-14 items-center px-4">
|
||||
<MainNav />
|
||||
<MobileNav />
|
||||
<div className="flex flex-1 items-center justify-between space-x-2 md:justify-end">
|
||||
<div className="flex flex-1 items-center justify-between gap-2 md:justify-end">
|
||||
<div className="w-full flex-1 md:w-auto md:flex-none">
|
||||
<CommandMenu />
|
||||
</div>
|
||||
<nav className="flex items-center">
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: "ghost",
|
||||
}),
|
||||
"h-8 w-8 px-0"
|
||||
)}
|
||||
<nav className="flex items-center gap-0.5">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 px-0">
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Icons.gitHub className="h-4 w-4" />
|
||||
<span className="sr-only">GitHub</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={siteConfig.links.twitter}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: "ghost",
|
||||
}),
|
||||
"h-8 w-8 px-0"
|
||||
)}
|
||||
>
|
||||
<Icons.twitter className="h-3 w-3 fill-current" />
|
||||
<span className="sr-only">Twitter</span>
|
||||
</div>
|
||||
</Link>
|
||||
<ModeToggle />
|
||||
</Link>
|
||||
</Button>
|
||||
<ModeSwitcher />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react"
|
||||
import template from "lodash.template"
|
||||
import { Check, Copy, HelpCircle, Moon, Repeat, Sun } from "lucide-react"
|
||||
import { Check, Copy, Moon, Repeat, Sun } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -72,75 +72,6 @@ export function ThemeCustomizer() {
|
||||
<Customizer />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="ml-2 hidden items-center gap-0.5">
|
||||
{mounted ? (
|
||||
<>
|
||||
{["zinc", "rose", "blue", "green", "orange"].map((color) => {
|
||||
const baseColor = baseColors.find(
|
||||
(baseColor) => baseColor.name === color
|
||||
)
|
||||
const isActive = config.theme === color
|
||||
|
||||
if (!baseColor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip key={baseColor.name}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
theme: baseColor.name,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"flex h-8 w-8 items-center justify-center rounded-full border-2 text-xs",
|
||||
isActive
|
||||
? "border-[--theme-primary]"
|
||||
: "border-transparent"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
baseColor?.activeColor[
|
||||
mode === "dark" ? "dark" : "light"
|
||||
]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-5 w-5 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && <Check className="h-4 w-4 text-white" />}
|
||||
</span>
|
||||
<span className="sr-only">{baseColor.label}</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
className="rounded-[0.5rem] bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{baseColor.label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<div className="mr-1 flex items-center gap-4">
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CopyCodeButton variant="ghost" size="sm" className="[&_svg]:hidden" />
|
||||
</div>
|
||||
@@ -164,10 +95,10 @@ function Customizer() {
|
||||
<div className="flex items-start pt-4 md:pt-0">
|
||||
<div className="space-y-1 pr-2">
|
||||
<div className="font-semibold leading-none tracking-tight">
|
||||
Customize
|
||||
Theme Customizer
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Pick a style and color for your components.
|
||||
Customize your components colors.
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -187,105 +118,53 @@ function Customizer() {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col space-y-4 md:space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex w-full items-center">
|
||||
<Label className="text-xs">Style</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<HelpCircle className="ml-1 h-3 w-3" />
|
||||
<span className="sr-only">About styles</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="space-y-3 rounded-[0.5rem] text-sm"
|
||||
side="right"
|
||||
align="start"
|
||||
alignOffset={-20}
|
||||
>
|
||||
<p className="font-medium">
|
||||
What is the difference between the New York and Default style?
|
||||
</p>
|
||||
<p>
|
||||
A style comes with its own set of components, animations,
|
||||
icons and more.
|
||||
</p>
|
||||
<p>
|
||||
The <span className="font-medium">Default</span> style has
|
||||
larger inputs, uses lucide-react for icons and
|
||||
tailwindcss-animate for animations.
|
||||
</p>
|
||||
<p>
|
||||
The <span className="font-medium">New York</span> style ships
|
||||
with smaller buttons and cards with shadows. It uses icons
|
||||
from Radix Icons.
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
onClick={() => setConfig({ ...config, style: "default" })}
|
||||
className={cn(
|
||||
config.style === "default" && "border-2 border-primary"
|
||||
)}
|
||||
>
|
||||
Default
|
||||
</Button>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
onClick={() => setConfig({ ...config, style: "new-york" })}
|
||||
className={cn(
|
||||
config.style === "new-york" && "border-2 border-primary"
|
||||
)}
|
||||
>
|
||||
New York
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Color</Label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{baseColors.map((theme) => {
|
||||
const isActive = config.theme === theme.name
|
||||
|
||||
return mounted ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
key={theme.name}
|
||||
onClick={() => {
|
||||
setConfig({
|
||||
...config,
|
||||
theme: theme.name,
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
"justify-start",
|
||||
isActive && "border-2 border-primary"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
theme?.activeColor[mode === "dark" ? "dark" : "light"]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && <Check className="h-4 w-4 text-white" />}
|
||||
</span>
|
||||
{theme.label}
|
||||
</Button>
|
||||
) : (
|
||||
<Skeleton className="h-8 w-full" key={theme.name} />
|
||||
{baseColors
|
||||
.filter(
|
||||
(theme) =>
|
||||
!["slate", "stone", "gray", "neutral"].includes(theme.name)
|
||||
)
|
||||
})}
|
||||
.map((theme) => {
|
||||
const isActive = config.theme === theme.name
|
||||
|
||||
return mounted ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
key={theme.name}
|
||||
onClick={() => {
|
||||
setConfig({
|
||||
...config,
|
||||
theme: theme.name,
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
"justify-start",
|
||||
isActive && "border-2 border-primary"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
theme?.activeColor[mode === "dark" ? "dark" : "light"]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && <Check className="h-4 w-4 text-white" />}
|
||||
</span>
|
||||
{theme.label}
|
||||
</Button>
|
||||
) : (
|
||||
<Skeleton className="h-8 w-full" key={theme.name} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -8,6 +8,10 @@ export interface DocsConfig {
|
||||
|
||||
export const docsConfig: DocsConfig = {
|
||||
mainNav: [
|
||||
{
|
||||
title: "Home",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
href: "/docs",
|
||||
|
||||
@@ -11,3 +11,8 @@ export const siteConfig = {
|
||||
}
|
||||
|
||||
export type SiteConfig = typeof siteConfig
|
||||
|
||||
export const META_THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#09090b",
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@ npm install next-themes
|
||||
|
||||
import * as React from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
```
|
||||
|
||||
25
apps/www/hooks/use-meta-color.ts
Normal file
25
apps/www/hooks/use-meta-color.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { META_THEME_COLORS } from "@/config/site"
|
||||
|
||||
export function useMetaColor() {
|
||||
const { resolvedTheme } = useTheme()
|
||||
|
||||
const metaColor = React.useMemo(() => {
|
||||
return resolvedTheme !== "dark"
|
||||
? META_THEME_COLORS.light
|
||||
: META_THEME_COLORS.dark
|
||||
}, [resolvedTheme])
|
||||
|
||||
const setMetaColor = React.useCallback((color: string) => {
|
||||
document
|
||||
.querySelector('meta[name="theme-color"]')
|
||||
?.setAttribute("content", color)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
metaColor,
|
||||
setMetaColor,
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,14 @@
|
||||
"use server"
|
||||
|
||||
import { promises as fs } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Index } from "@/__registry__"
|
||||
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
|
||||
import { z } from "zod"
|
||||
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { BlockChunk, blockSchema, registryEntrySchema } from "@/registry/schema"
|
||||
import { registryEntrySchema } from "@/registry/schema"
|
||||
|
||||
const DEFAULT_BLOCKS_STYLE = "new-york" satisfies Style["name"]
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
const BLOCKS_WHITELIST_PREFIXES = ["sidebar", "login"]
|
||||
const REGISTRY_BLOCK_TYPES = ["registry:block"]
|
||||
|
||||
export async function getAllBlockIds(
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
@@ -24,136 +17,14 @@ export async function getAllBlockIds(
|
||||
return blocks.map((block) => block.name)
|
||||
}
|
||||
|
||||
export async function getBlock(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
) {
|
||||
const entry = Index[style][name]
|
||||
|
||||
const content = await _getBlockContent(name, style)
|
||||
|
||||
const chunks = entry?.chunks
|
||||
? await Promise.all(
|
||||
entry?.chunks?.map(async (chunk: BlockChunk) => {
|
||||
const code = await readFile(chunk.file)
|
||||
|
||||
const tempFile = await createTempSourceFile(`${chunk.name}.tsx`)
|
||||
const sourceFile = project.createSourceFile(tempFile, code, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
|
||||
.filter((node) => {
|
||||
return node.getAttribute("x-chunk") !== undefined
|
||||
})
|
||||
?.map((component) => {
|
||||
component
|
||||
.getAttribute("x-chunk")
|
||||
?.asKind(SyntaxKind.JsxAttribute)
|
||||
?.remove()
|
||||
})
|
||||
|
||||
return {
|
||||
...chunk,
|
||||
code: sourceFile
|
||||
.getText()
|
||||
.replaceAll(`@/registry/${style}/`, "@/components/"),
|
||||
}
|
||||
})
|
||||
)
|
||||
: []
|
||||
|
||||
const block = {
|
||||
style,
|
||||
highlightedCode: content.code ? await highlightCode(content.code) : "",
|
||||
...entry,
|
||||
...content,
|
||||
chunks,
|
||||
type: "registry:block",
|
||||
}
|
||||
|
||||
const result = blockSchema.safeParse(block)
|
||||
|
||||
if (!result.success) {
|
||||
console.log(block)
|
||||
return null
|
||||
}
|
||||
|
||||
return result.data
|
||||
}
|
||||
|
||||
async function _getAllBlocks(style: Style["name"] = DEFAULT_BLOCKS_STYLE) {
|
||||
const index = z.record(registryEntrySchema).parse(Index[style])
|
||||
|
||||
return Object.values(index).filter((block) => block.type === "registry:block")
|
||||
}
|
||||
|
||||
async function _getBlockCode(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
) {
|
||||
const entry = Index[style][name]
|
||||
if (!entry) {
|
||||
console.error(`Block ${name} not found in style ${style}`)
|
||||
return ""
|
||||
}
|
||||
const block = registryEntrySchema.parse(entry)
|
||||
|
||||
if (!block.source) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return await readFile(block.source)
|
||||
}
|
||||
|
||||
async function readFile(source: string) {
|
||||
const filepath = path.join(process.cwd(), source)
|
||||
return await fs.readFile(filepath, "utf-8")
|
||||
}
|
||||
|
||||
async function createTempSourceFile(filename: string) {
|
||||
const dir = await fs.mkdtemp(path.join(tmpdir(), "codex-"))
|
||||
return path.join(dir, filename)
|
||||
}
|
||||
|
||||
async function _getBlockContent(name: string, style: Style["name"]) {
|
||||
const raw = await _getBlockCode(name, style)
|
||||
|
||||
const tempFile = await createTempSourceFile(`${name}.tsx`)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
// Extract meta.
|
||||
const iframeHeight = _extractVariable(sourceFile, "iframeHeight")
|
||||
const containerClassName = _extractVariable(sourceFile, "containerClassName")
|
||||
|
||||
// Format the code.
|
||||
let code = sourceFile.getText()
|
||||
code = code.replaceAll(`@/registry/${style}/`, "@/components/")
|
||||
code = code.replaceAll("export default", "export")
|
||||
|
||||
return {
|
||||
code,
|
||||
container: {
|
||||
height: iframeHeight,
|
||||
className: containerClassName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function _extractVariable(sourceFile: SourceFile, name: string) {
|
||||
const variable = sourceFile.getVariableDeclaration(name)
|
||||
if (!variable) {
|
||||
return null
|
||||
}
|
||||
|
||||
const value = variable
|
||||
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
||||
.getLiteralValue()
|
||||
|
||||
variable.remove()
|
||||
|
||||
return value
|
||||
return Object.values(index).filter((block) =>
|
||||
BLOCKS_WHITELIST_PREFIXES.some(
|
||||
(prefix) =>
|
||||
block.name.startsWith(prefix) &&
|
||||
REGISTRY_BLOCK_TYPES.includes(block.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
// import { JetBrains_Mono as FontMono, Inter as FontSans } from "next/font/google"
|
||||
import { JetBrains_Mono as FontMono } from "next/font/google"
|
||||
// import { GeistMono } from "geist/font/mono"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
|
||||
// export const fontSans = FontSans({
|
||||
// subsets: ["latin"],
|
||||
// variable: "--font-sans",
|
||||
// })
|
||||
export const fontSans = GeistSans
|
||||
|
||||
export const fontMono = FontMono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-mono",
|
||||
})
|
||||
export const fontMono = GeistMono
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use server"
|
||||
|
||||
import { codeToHtml } from "shiki"
|
||||
|
||||
export async function highlightCode(code: string) {
|
||||
const html = codeToHtml(code, {
|
||||
lang: "typescript",
|
||||
const html = await codeToHtml(code, {
|
||||
lang: "jsx",
|
||||
theme: "github-dark-default",
|
||||
transformers: [
|
||||
{
|
||||
|
||||
37
apps/www/lib/registry.test.ts
Normal file
37
apps/www/lib/registry.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { createFileTreeForRegistryItemFiles } from "@/lib/registry"
|
||||
|
||||
describe("createFileTreeForRegistryItemFiles", () => {
|
||||
it("should create a nested file tree structure", async () => {
|
||||
const files = [
|
||||
{ path: "page.tsx" },
|
||||
{ path: "components/foo.tsx" },
|
||||
{ path: "components/baz.tsx" },
|
||||
{ path: "components/boo/quip.tsx" },
|
||||
]
|
||||
|
||||
const expectedOutput = [
|
||||
{ name: "page.tsx", path: "page.tsx" },
|
||||
{
|
||||
name: "components",
|
||||
children: [
|
||||
{ name: "foo.tsx", path: "components/foo.tsx" },
|
||||
{ name: "baz.tsx", path: "components/baz.tsx" },
|
||||
{
|
||||
name: "boo",
|
||||
children: [{ name: "quip.tsx", path: "components/boo/quip.tsx" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = await createFileTreeForRegistryItemFiles(files)
|
||||
expect(result).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it("should return an empty array for empty input", async () => {
|
||||
const result = await createFileTreeForRegistryItemFiles([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
270
apps/www/lib/registry.ts
Normal file
270
apps/www/lib/registry.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { promises as fs } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Index } from "@/__registry__"
|
||||
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { registryEntrySchema, registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
export const DEFAULT_REGISTRY_STYLE = "new-york" satisfies Style["name"]
|
||||
|
||||
const memoizedIndex: typeof Index = Object.fromEntries(
|
||||
Object.entries(Index).map(([style, items]) => [style, { ...items }])
|
||||
)
|
||||
|
||||
export function getRegistryComponent(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_REGISTRY_STYLE
|
||||
) {
|
||||
return memoizedIndex[style][name]?.component
|
||||
}
|
||||
|
||||
export async function getRegistryItem(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_REGISTRY_STYLE
|
||||
) {
|
||||
const item = memoizedIndex[style][name]
|
||||
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Convert all file paths to object.
|
||||
// TODO: remove when we migrate to new registry.
|
||||
item.files = item.files.map((file: unknown) =>
|
||||
typeof file === "string" ? { path: file } : file
|
||||
)
|
||||
|
||||
// Fail early before doing expensive file operations.
|
||||
const result = registryEntrySchema.safeParse(item)
|
||||
if (!result.success) {
|
||||
return null
|
||||
}
|
||||
|
||||
let files: typeof result.data.files = []
|
||||
for (const file of item.files) {
|
||||
const content = await getFileContent(file.path)
|
||||
const relativePath = path.relative(process.cwd(), file.path)
|
||||
|
||||
files.push({
|
||||
...file,
|
||||
path: relativePath,
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
// Get meta.
|
||||
// Assume the first file is the main file.
|
||||
const meta = await getFileMeta(files[0].path)
|
||||
|
||||
// Fix file paths.
|
||||
files = fixFilePaths(files)
|
||||
|
||||
const parsed = registryEntrySchema.safeParse({
|
||||
...result.data,
|
||||
files,
|
||||
meta,
|
||||
})
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error(parsed.error.message)
|
||||
return null
|
||||
}
|
||||
|
||||
return parsed.data
|
||||
}
|
||||
|
||||
async function getFileContent(filePath: string) {
|
||||
const raw = await fs.readFile(filePath, "utf-8")
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
|
||||
const tempFile = await createTempSourceFile(filePath)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
// Remove meta variables.
|
||||
removeVariable(sourceFile, "iframeHeight")
|
||||
removeVariable(sourceFile, "containerClassName")
|
||||
removeVariable(sourceFile, "description")
|
||||
|
||||
let code = sourceFile.getFullText()
|
||||
|
||||
// Format the code.
|
||||
code = code.replaceAll("export default", "export")
|
||||
|
||||
// Fix imports.
|
||||
code = fixImport(code)
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
async function getFileMeta(filePath: string) {
|
||||
const raw = await fs.readFile(filePath, "utf-8")
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
|
||||
const tempFile = await createTempSourceFile(filePath)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
const iframeHeight = extractVariable(sourceFile, "iframeHeight")
|
||||
const containerClassName = extractVariable(sourceFile, "containerClassName")
|
||||
const description = extractVariable(sourceFile, "description")
|
||||
|
||||
return {
|
||||
iframeHeight,
|
||||
containerClassName,
|
||||
description,
|
||||
}
|
||||
}
|
||||
|
||||
function getFileTarget(file: z.infer<typeof registryItemFileSchema>) {
|
||||
let target = file.target
|
||||
|
||||
if (!target || target === "") {
|
||||
const fileName = file.path.split("/").pop()
|
||||
if (
|
||||
file.type === "registry:block" ||
|
||||
file.type === "registry:component" ||
|
||||
file.type === "registry:example"
|
||||
) {
|
||||
target = `components/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:ui") {
|
||||
target = `components/ui/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:hook") {
|
||||
target = `hooks/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:lib") {
|
||||
target = `lib/${fileName}`
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
async function createTempSourceFile(filename: string) {
|
||||
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
|
||||
return path.join(dir, filename)
|
||||
}
|
||||
|
||||
function removeVariable(sourceFile: SourceFile, name: string) {
|
||||
sourceFile.getVariableDeclaration(name)?.remove()
|
||||
}
|
||||
|
||||
function extractVariable(sourceFile: SourceFile, name: string) {
|
||||
const variable = sourceFile.getVariableDeclaration(name)
|
||||
if (!variable) {
|
||||
return null
|
||||
}
|
||||
|
||||
const value = variable
|
||||
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
||||
.getLiteralValue()
|
||||
|
||||
variable.remove()
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function fixFilePaths(files: z.infer<typeof registryEntrySchema>["files"]) {
|
||||
if (!files) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Resolve all paths relative to the first file's directory.
|
||||
const firstFilePath = files[0].path
|
||||
const firstFilePathDir = path.dirname(firstFilePath)
|
||||
|
||||
return files.map((file) => {
|
||||
return {
|
||||
...file,
|
||||
path: path.relative(firstFilePathDir, file.path),
|
||||
target: getFileTarget(file),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function fixImport(content: string) {
|
||||
const regex = /@\/(.+?)\/((?:.*?\/)?(?:components|ui|hooks|lib))\/([\w-]+)/g
|
||||
|
||||
const replacement = (
|
||||
match: string,
|
||||
path: string,
|
||||
type: string,
|
||||
component: string
|
||||
) => {
|
||||
if (type.endsWith("components")) {
|
||||
return `@/components/${component}`
|
||||
} else if (type.endsWith("ui")) {
|
||||
return `@/components/ui/${component}`
|
||||
} else if (type.endsWith("hooks")) {
|
||||
return `@/hooks/${component}`
|
||||
} else if (type.endsWith("lib")) {
|
||||
return `@/lib/${component}`
|
||||
}
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
return content.replace(regex, replacement)
|
||||
}
|
||||
|
||||
export type FileTree = {
|
||||
name: string
|
||||
path?: string
|
||||
children?: FileTree[]
|
||||
}
|
||||
|
||||
export function createFileTreeForRegistryItemFiles(
|
||||
files: Array<{ path: string; target?: string }>
|
||||
) {
|
||||
const root: FileTree[] = []
|
||||
|
||||
for (const file of files) {
|
||||
const path = file.target ?? file.path
|
||||
const parts = path.split("/")
|
||||
let currentLevel = root
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
const isFile = i === parts.length - 1
|
||||
const existingNode = currentLevel.find((node) => node.name === part)
|
||||
|
||||
if (existingNode) {
|
||||
if (isFile) {
|
||||
// Update existing file node with full path
|
||||
existingNode.path = path
|
||||
} else {
|
||||
// Move to next level in the tree
|
||||
currentLevel = existingNode.children!
|
||||
}
|
||||
} else {
|
||||
const newNode: FileTree = isFile
|
||||
? { name: part, path }
|
||||
: { name: part, children: [] }
|
||||
|
||||
currentLevel.push(newNode)
|
||||
|
||||
if (!isFile) {
|
||||
currentLevel = newNode.children!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
@@ -42,12 +42,12 @@ export function rehypeComponent() {
|
||||
file.endsWith(`${fileName}.tsx`) ||
|
||||
file.endsWith(`${fileName}.ts`)
|
||||
)
|
||||
}) || component.files[0]
|
||||
: component.files[0]
|
||||
}) || component.files[0]?.path
|
||||
: component.files[0]?.path
|
||||
}
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
const filePath = src
|
||||
let source = fs.readFileSync(filePath, "utf8")
|
||||
|
||||
// Replace imports.
|
||||
@@ -106,10 +106,10 @@ export function rehypeComponent() {
|
||||
try {
|
||||
for (const style of styles) {
|
||||
const component = Index[style.name][name]
|
||||
const src = component.files[0]
|
||||
const src = component.files[0]?.path
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
const filePath = src
|
||||
let source = fs.readFileSync(filePath, "utf8")
|
||||
|
||||
// Replace imports.
|
||||
|
||||
@@ -2,6 +2,11 @@ import { createContentlayerPlugin } from "next-contentlayer2"
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
outputFileTracingIncludes: {
|
||||
"/blocks/*": ["./registry/**/*"],
|
||||
},
|
||||
},
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
images: {
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"markdown-wasm": "^1.2.0",
|
||||
"next": "14.3.0-canary.43",
|
||||
"next-contentlayer2": "^0.4.6",
|
||||
"next-themes": "^0.2.1",
|
||||
"next-themes": "^0.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -85,7 +85,7 @@
|
||||
"swr": "2.2.6-beta.3",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"ts-morph": "^22.0.0",
|
||||
"vaul": "0.9.0",
|
||||
"vaul": "1.1.1",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "authentication-01",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/authentication-01.tsx",
|
||||
"content": "import { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function LoginForm() {\n return (\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>\n Enter your email below to login to your account.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"m@example.com\" required />\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"password\">Password</Label>\n <Input id=\"password\" type=\"password\" required />\n </div>\n </CardContent>\n <CardFooter>\n <Button className=\"w-full\">Sign in</Button>\n </CardFooter>\n </Card>\n )\n}\n",
|
||||
"type": "registry:block",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "authentication-02",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/authentication-02.tsx",
|
||||
"content": "import Link from \"next/link\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function LoginForm() {\n return (\n <Card className=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>\n Enter your email below to login to your account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <Link href=\"#\" className=\"ml-auto inline-block text-sm underline\">\n Forgot your password?\n </Link>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{\" \"}\n <Link href=\"#\" className=\"underline\">\n Sign up\n </Link>\n </div>\n </CardContent>\n </Card>\n )\n}\n",
|
||||
"type": "registry:block",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "authentication-03",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/authentication-03.tsx",
|
||||
"content": "import Link from \"next/link\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function LoginForm() {\n return (\n <Card className=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-xl\">Sign Up</CardTitle>\n <CardDescription>\n Enter your information to create an account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"first-name\">First name</Label>\n <Input id=\"first-name\" placeholder=\"Max\" required />\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"last-name\">Last name</Label>\n <Input id=\"last-name\" placeholder=\"Robinson\" required />\n </div>\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <Label htmlFor=\"password\">Password</Label>\n <Input id=\"password\" type=\"password\" />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Create an account\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Sign up with GitHub\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{\" \"}\n <Link href=\"#\" className=\"underline\">\n Sign in\n </Link>\n </div>\n </CardContent>\n </Card>\n )\n}\n",
|
||||
"type": "registry:block",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "authentication-04",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/authentication-04.tsx",
|
||||
"content": "import Image from \"next/image\"\nimport Link from \"next/link\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function Dashboard() {\n return (\n <div className=\"w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]\">\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"mx-auto grid w-[350px] gap-6\">\n <div className=\"grid gap-2 text-center\">\n <h1 className=\"text-3xl font-bold\">Login</h1>\n <p className=\"text-balance text-muted-foreground\">\n Enter your email below to login to your account\n </p>\n </div>\n <div className=\"grid gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <Link\n href=\"/forgot-password\"\n className=\"ml-auto inline-block text-sm underline\"\n >\n Forgot your password?\n </Link>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{\" \"}\n <Link href=\"#\" className=\"underline\">\n Sign up\n </Link>\n </div>\n </div>\n </div>\n <div className=\"hidden bg-muted lg:block\">\n <Image\n src=\"/placeholder.svg\"\n alt=\"Image\"\n width=\"1920\"\n height=\"1080\"\n className=\"h-full w-full object-cover dark:brightness-[0.2] dark:grayscale\"\n />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:block",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-controlled.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n Frame,\n LifeBuoy,\n Map,\n PanelLeftClose,\n PanelLeftOpen,\n PieChart,\n Send,\n} from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n },\n]\n\nexport default function AppSidebar() {\n const [open, setOpen] = React.useState(true)\n\n return (\n <SidebarProvider open={open} onOpenChange={setOpen}>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {projects.map((project) => (\n <SidebarMenuItem key={project.name}>\n <SidebarMenuButton asChild>\n <a href={project.url}>\n <project.icon />\n <span>{project.name}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>\n <header className=\"flex items-center h-12 px-4 justify-between\">\n <Button\n onClick={() => setOpen((open) => !open)}\n size=\"sm\"\n variant=\"ghost\"\n >\n {open ? <PanelLeftClose /> : <PanelLeftOpen />}\n <span>{open ? \"Close\" : \"Open\"} Sidebar</span>\n </Button>\n </header>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-footer.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronDown, ChevronUp, User2 } from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarHeader />\n <SidebarContent />\n <SidebarFooter>\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\">\n Username\n <ChevronUp className=\"ml-auto\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side=\"top\"\n className=\"w-[--radix-popper-anchor-width]\"\n >\n <DropdownMenuItem>\n <span>Account</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <span>Billing</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <span>Sign out</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n </Sidebar>\n <SidebarInset>\n <header className=\"flex items-center justify-between px-4 h-12\">\n <SidebarTrigger />\n </header>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-group-action.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n ChevronDown,\n Frame,\n LifeBuoy,\n Map,\n PieChart,\n Plus,\n Send,\n} from \"lucide-react\"\nimport { Toaster, toast } from \"sonner\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Toaster\n position=\"bottom-left\"\n toastOptions={{\n className: \"ml-[160px]\",\n }}\n />\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupAction\n title=\"Add Project\"\n onClick={() => toast(\"You clicked the group action!\")}\n >\n <Plus /> <span className=\"sr-only\">Add Project</span>\n </SidebarGroupAction>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton asChild>\n <a href=\"#\">\n <Frame />\n <span>Design Engineering</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton asChild>\n <a href=\"#\">\n <PieChart />\n <span>Sales & Marketing</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton asChild>\n <a href=\"#\">\n <Map />\n <span>Travel</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-group-collapsible.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronDown, LifeBuoy, Send } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <Collapsible defaultOpen className=\"group/collapsible\">\n <SidebarGroup>\n <SidebarGroupLabel\n asChild\n className=\"text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\"\n >\n <CollapsibleTrigger>\n Help\n <ChevronDown className=\"ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180\" />\n </CollapsibleTrigger>\n </SidebarGroupLabel>\n <CollapsibleContent>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <LifeBuoy />\n Support\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <Send />\n Feedback\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </CollapsibleContent>\n </SidebarGroup>\n </Collapsible>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-group.tsx",
|
||||
"content": "\"use client\"\n\nimport { LifeBuoy, Send } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Help</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <LifeBuoy />\n Support\n </SidebarMenuButton>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <Send />\n Feedback\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-header.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronDown } from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n Sidebar,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\">\n Select Workspace\n <ChevronDown className=\"ml-auto\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"w-[--radix-popper-anchor-width]\">\n <DropdownMenuItem>\n <span>Acme Inc</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <span>Acme Corp.</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n </Sidebar>\n <SidebarInset>\n <header className=\"flex items-center justify-between px-4 h-12\">\n <SidebarTrigger />\n </header>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-menu-action.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n Frame,\n LifeBuoy,\n Map,\n MoreHorizontal,\n PieChart,\n Send,\n} from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {projects.map((project, index) => (\n <SidebarMenuItem key={project.name}>\n <SidebarMenuButton\n asChild\n className=\"group-has-[[data-state=open]]/menu-item:bg-sidebar-accent\"\n >\n <a href={project.url}>\n <project.icon />\n <span>{project.name}</span>\n </a>\n </SidebarMenuButton>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuAction>\n <MoreHorizontal />\n <span className=\"sr-only\">More</span>\n </SidebarMenuAction>\n </DropdownMenuTrigger>\n <DropdownMenuContent side=\"right\" align=\"start\">\n <DropdownMenuItem>\n <span>Edit Project</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <span>Delete Project</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-menu-badge.tsx",
|
||||
"content": "\"use client\"\n\nimport { Frame, LifeBuoy, Map, PieChart, Send } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n badge: \"24\",\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n badge: \"12\",\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n badge: \"3\",\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n badge: \"21\",\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n badge: \"8\",\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {projects.map((project) => (\n <SidebarMenuItem key={project.name}>\n <SidebarMenuButton\n asChild\n className=\"group-has-[[data-state=open]]/menu-item:bg-sidebar-accent\"\n >\n <a href={project.url}>\n <project.icon />\n <span>{project.name}</span>\n </a>\n </SidebarMenuButton>\n <SidebarMenuBadge>{project.badge}</SidebarMenuBadge>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-menu-collapsible.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronRight } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst items = [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupContent>\n <SidebarMenu>\n {items.map((item, index) => (\n <Collapsible\n key={index}\n className=\"group/collapsible\"\n defaultOpen={index === 0}\n >\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton>\n <span>{item.title}</span>\n <ChevronRight className=\"transition-transform ml-auto group-data-[state=open]/collapsible:rotate-90\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.items.map((subItem, subIndex) => (\n <SidebarMenuSubItem key={subIndex}>\n <SidebarMenuSubButton asChild>\n <a href={subItem.url}>\n <span>{subItem.title}</span>\n </a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-menu-sub.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst items = [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupContent>\n <SidebarMenu>\n {items.map((item, index) => (\n <SidebarMenuItem key={index}>\n <SidebarMenuButton asChild>\n <a href={item.url}>\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n <SidebarMenuSub>\n {item.items.map((subItem, subIndex) => (\n <SidebarMenuSubItem key={subIndex}>\n <SidebarMenuSubButton asChild>\n <a href={subItem.url}>\n <span>{subItem.title}</span>\n </a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-menu.tsx",
|
||||
"content": "\"use client\"\n\nimport { Frame, LifeBuoy, Map, PieChart, Send } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {projects.map((project) => (\n <SidebarMenuItem key={project.name}>\n <SidebarMenuButton asChild>\n <a href={project.url}>\n <project.icon />\n <span>{project.name}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar-rsc.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { Frame, LifeBuoy, Map, PieChart, Send } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n badge: \"24\",\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n badge: \"12\",\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n badge: \"3\",\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n badge: \"21\",\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n badge: \"8\",\n },\n]\n\n// Dummy fetch function\nasync function fetchProjects() {\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return projects\n}\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarGroupContent>\n <React.Suspense fallback={<NavProjectsSkeleton />}>\n <NavProjects />\n </React.Suspense>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n </SidebarProvider>\n )\n}\n\nfunction NavProjectsSkeleton() {\n return (\n <SidebarMenu>\n {Array.from({ length: 5 }).map((_, index) => (\n <SidebarMenuItem key={index}>\n <SidebarMenuSkeleton showIcon />\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n )\n}\n\nasync function NavProjects() {\n const projects = await fetchProjects()\n\n return (\n <SidebarMenu>\n {projects.map((project) => (\n <SidebarMenuItem key={project.name}>\n <SidebarMenuButton asChild>\n <a href={project.url}>\n <project.icon />\n <span>{project.name}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
{
|
||||
"path": "block/demo-sidebar.tsx",
|
||||
"content": "\"use client\"\n\nimport { Calendar, Home, Inbox, Search, Settings } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/registry/default/ui/sidebar\"\n\n// Menu items.\nconst items = [\n {\n title: \"Home\",\n url: \"#\",\n icon: Home,\n },\n {\n title: \"Inbox\",\n url: \"#\",\n icon: Inbox,\n },\n {\n title: \"Calendar\",\n url: \"#\",\n icon: Calendar,\n },\n {\n title: \"Search\",\n url: \"#\",\n icon: Search,\n },\n {\n title: \"Settings\",\n url: \"#\",\n icon: Settings,\n },\n]\n\nexport default function AppSidebar() {\n return (\n <SidebarProvider>\n <Sidebar>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Application</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {items.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild>\n <a href={item.url}>\n <item.icon />\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <SidebarInset>\n <header className=\"flex items-center justify-between px-4 h-12\">\n <SidebarTrigger />\n </header>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "ui/input.tsx",
|
||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
||||
"type": "registry:ui",
|
||||
"target": ""
|
||||
}
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "block/login-01/page.tsx",
|
||||
"content": "import { LoginForm } from \"@/registry/default/block/login-01/components/login-form\"\n\nexport default function Page() {\n return (\n <div className=\"flex h-screen w-full items-center justify-center px-4\">\n <LoginForm />\n </div>\n )\n}\n",
|
||||
"content": "import { LoginForm } from \"@/registry/default/block/login-01/components/login-form\"\n\nexport default function Page() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <LoginForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:page",
|
||||
"target": "app/login/page.tsx"
|
||||
},
|
||||
{
|
||||
"path": "block/login-01/components/login-form.tsx",
|
||||
"content": "import Link from \"next/link\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport function LoginForm() {\n return (\n <Card className=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>\n Enter your email below to login to your account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-4\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <Link href=\"#\" className=\"ml-auto inline-block text-sm underline\">\n Forgot your password?\n </Link>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{\" \"}\n <Link href=\"#\" className=\"underline\">\n Sign up\n </Link>\n </div>\n </CardContent>\n </Card>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"content": "import { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport function LoginForm({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<\"div\">) {\n return (\n <div className={cn(\"flex flex-col gap-6\", className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>\n Enter your email below to login to your account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <a\n href=\"#\"\n className=\"ml-auto inline-block text-sm underline-offset-4 hover:underline\"\n >\n Forgot your password?\n </a>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{\" \"}\n <a href=\"#\" className=\"underline underline-offset-4\">\n Sign up\n </a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
25
apps/www/public/r/styles/default/login-02.json
Normal file
25
apps/www/public/r/styles/default/login-02.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "login-02",
|
||||
"type": "registry:block",
|
||||
"description": "A two column login page with a cover image.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/login-02/page.tsx",
|
||||
"content": "import { GalleryVerticalEnd } from \"lucide-react\"\n\nimport { LoginForm } from \"@/registry/default/block/login-02/components/login-form\"\n\nexport default function LoginPage() {\n return (\n <div className=\"grid min-h-svh lg:grid-cols-2\">\n <div className=\"flex flex-col gap-4 p-6 md:p-10\">\n <div className=\"flex justify-center gap-2 md:justify-start\">\n <a href=\"#\" className=\"flex items-center gap-2 font-medium\">\n <div className=\"flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n Acme Inc.\n </a>\n </div>\n <div className=\"flex flex-1 items-center justify-center\">\n <div className=\"w-full max-w-xs\">\n <LoginForm />\n </div>\n </div>\n </div>\n <div className=\"relative hidden bg-muted lg:block\">\n <img\n src=\"/placeholder.svg\"\n alt=\"Image\"\n className=\"absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale\"\n />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:page",
|
||||
"target": "app/login/page.tsx"
|
||||
},
|
||||
{
|
||||
"path": "block/login-02/components/login-form.tsx",
|
||||
"content": "import { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport function LoginForm({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<\"form\">) {\n return (\n <form className={cn(\"flex flex-col gap-6\", className)} {...props}>\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <h1 className=\"text-2xl font-bold\">Login to your account</h1>\n <p className=\"text-balance text-sm text-muted-foreground\">\n Enter your email below to login to your account\n </p>\n </div>\n <div className=\"grid gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"m@example.com\" required />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <a\n href=\"#\"\n className=\"ml-auto text-sm underline-offset-4 hover:underline\"\n >\n Forgot your password?\n </a>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n <div className=\"relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border\">\n <span className=\"relative z-10 bg-background px-2 text-muted-foreground\">\n Or continue with\n </span>\n </div>\n <Button variant=\"outline\" className=\"w-full\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path\n d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"\n fill=\"currentColor\"\n />\n </svg>\n Login with GitHub\n </Button>\n </div>\n <div className=\"text-center text-sm\">\n Don't have an account?{\" \"}\n <a href=\"#\" className=\"underline underline-offset-4\">\n Sign up\n </a>\n </div>\n </form>\n )\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
25
apps/www/public/r/styles/default/login-03.json
Normal file
25
apps/www/public/r/styles/default/login-03.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "login-03",
|
||||
"type": "registry:block",
|
||||
"description": "A login page with a muted background color.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/login-03/page.tsx",
|
||||
"content": "import { GalleryVerticalEnd } from \"lucide-react\"\n\nimport { LoginForm } from \"@/registry/default/block/login-03/components/login-form\"\n\nexport default function LoginPage() {\n return (\n <div className=\"flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10\">\n <div className=\"flex w-full max-w-sm flex-col gap-6\">\n <a href=\"#\" className=\"flex items-center gap-2 self-center font-medium\">\n <div className=\"flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n Acme Inc.\n </a>\n <LoginForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:page",
|
||||
"target": "app/login/page.tsx"
|
||||
},
|
||||
{
|
||||
"path": "block/login-03/components/login-form.tsx",
|
||||
"content": "import { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport function LoginForm({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<\"div\">) {\n return (\n <div className={cn(\"flex flex-col gap-6\", className)} {...props}>\n <Card>\n <CardHeader className=\"text-center\">\n <CardTitle className=\"text-xl\">Welcome back</CardTitle>\n <CardDescription>\n Login with your Apple or Google account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form>\n <div className=\"grid gap-6\">\n <div className=\"flex flex-col gap-4\">\n <Button variant=\"outline\" className=\"w-full\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path\n d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"\n fill=\"currentColor\"\n />\n </svg>\n Login with Apple\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path\n d=\"M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z\"\n fill=\"currentColor\"\n />\n </svg>\n Login with Google\n </Button>\n </div>\n <div className=\"relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border\">\n <span className=\"relative z-10 bg-background px-2 text-muted-foreground\">\n Or continue with\n </span>\n </div>\n <div className=\"grid gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <a\n href=\"#\"\n className=\"ml-auto text-sm underline-offset-4 hover:underline\"\n >\n Forgot your password?\n </a>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n </div>\n <div className=\"text-center text-sm\">\n Don't have an account?{\" \"}\n <a href=\"#\" className=\"underline underline-offset-4\">\n Sign up\n </a>\n </div>\n </div>\n </form>\n </CardContent>\n </Card>\n <div className=\"text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary \">\n By clicking continue, you agree to our <a href=\"#\">Terms of Service</a>{\" \"}\n and <a href=\"#\">Privacy Policy</a>.\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
25
apps/www/public/r/styles/default/login-04.json
Normal file
25
apps/www/public/r/styles/default/login-04.json
Normal file
File diff suppressed because one or more lines are too long
25
apps/www/public/r/styles/default/login-05.json
Normal file
25
apps/www/public/r/styles/default/login-05.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "login-05",
|
||||
"type": "registry:block",
|
||||
"description": "A simple email-only login page.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "block/login-05/page.tsx",
|
||||
"content": "import { LoginForm } from \"@/registry/default/block/login-05/components/login-form\"\n\nexport default function LoginPage() {\n return (\n <div className=\"flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <LoginForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:page",
|
||||
"target": "app/login/page.tsx"
|
||||
},
|
||||
{
|
||||
"path": "block/login-05/components/login-form.tsx",
|
||||
"content": "import { GalleryVerticalEnd } from \"lucide-react\"\n\nimport { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport function LoginForm({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<\"div\">) {\n return (\n <div className={cn(\"flex flex-col gap-6\", className)} {...props}>\n <form>\n <div className=\"flex flex-col gap-6\">\n <div className=\"flex flex-col items-center gap-2\">\n <a\n href=\"#\"\n className=\"flex flex-col items-center gap-2 font-medium\"\n >\n <div className=\"flex h-8 w-8 items-center justify-center rounded-md\">\n <GalleryVerticalEnd className=\"size-6\" />\n </div>\n <span className=\"sr-only\">Acme Inc.</span>\n </a>\n <h1 className=\"text-xl font-bold\">Welcome to Acme Inc.</h1>\n <div className=\"text-center text-sm\">\n Don't have an account?{\" \"}\n <a href=\"#\" className=\"underline underline-offset-4\">\n Sign up\n </a>\n </div>\n </div>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Login\n </Button>\n </div>\n <div className=\"relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border\">\n <span className=\"relative z-10 bg-background px-2 text-muted-foreground\">\n Or\n </span>\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Button variant=\"outline\" className=\"w-full\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path\n d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"\n fill=\"currentColor\"\n />\n </svg>\n Continue with Apple\n </Button>\n <Button variant=\"outline\" className=\"w-full\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path\n d=\"M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z\"\n fill=\"currentColor\"\n />\n </svg>\n Continue with Google\n </Button>\n </div>\n </div>\n </form>\n <div className=\"text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary \">\n By clicking continue, you agree to our <a href=\"#\">Terms of Service</a>{\" \"}\n and <a href=\"#\">Privacy Policy</a>.\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -19,17 +19,20 @@
|
||||
{
|
||||
"path": "block/sidebar-01/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\n\nimport { SearchForm } from \"@/registry/default/block/sidebar-01/components/search-form\"\nimport { VersionSwitcher } from \"@/registry/default/block/sidebar-01/components/version-switcher\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n versions: [\"1.0.1\", \"1.1.0-alpha\", \"2.0.0-beta1\"],\n navMain: [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarHeader>\n <VersionSwitcher\n versions={data.versions}\n defaultVersion={data.versions[0]}\n />\n <SearchForm />\n </SidebarHeader>\n <SidebarContent>\n {/* We create a SidebarGroup for each parent. */}\n {data.navMain.map((item) => (\n <SidebarGroup key={item.title}>\n <SidebarGroupLabel>{item.title}</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {item.items.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild isActive={item.isActive}>\n <a href={item.url}>{item.title}</a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n ))}\n </SidebarContent>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-01/components/search-form.tsx",
|
||||
"content": "import { Search } from \"lucide-react\"\n\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n SidebarGroup,\n SidebarGroupContent,\n SidebarInput,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function SearchForm({ ...props }: React.ComponentProps<\"form\">) {\n return (\n <form {...props}>\n <SidebarGroup className=\"py-0\">\n <SidebarGroupContent className=\"relative\">\n <Label htmlFor=\"search\" className=\"sr-only\">\n Search\n </Label>\n <SidebarInput\n id=\"search\"\n placeholder=\"Search the docs...\"\n className=\"pl-8\"\n />\n <Search className=\"pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 select-none opacity-50\" />\n </SidebarGroupContent>\n </SidebarGroup>\n </form>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-01/components/version-switcher.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ChevronsUpDown, GalleryVerticalEnd } from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function VersionSwitcher({\n versions,\n defaultVersion,\n}: {\n versions: string[]\n defaultVersion: string\n}) {\n const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion)\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n <div className=\"flex flex-col gap-0.5 leading-none\">\n <span className=\"font-semibold\">Documentation</span>\n <span className=\"\">v{selectedVersion}</span>\n </div>\n <ChevronsUpDown className=\"ml-auto\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width]\"\n align=\"start\"\n >\n {versions.map((version) => (\n <DropdownMenuItem\n key={version}\n onSelect={() => setSelectedVersion(version)}\n >\n v{version}{\" \"}\n {version === selectedVersion && <Check className=\"ml-auto\" />}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,8 @@
|
||||
{
|
||||
"path": "block/sidebar-03/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { GalleryVerticalEnd } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n navMain: [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Community\",\n url: \"#\",\n items: [\n {\n title: \"Contribution Guide\",\n url: \"#\",\n },\n ],\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild>\n <a href=\"#\">\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n <div className=\"flex flex-col gap-0.5 leading-none\">\n <span className=\"font-semibold\">Documentation</span>\n <span className=\"\">v1.0.0</span>\n </div>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n <SidebarContent>\n <SidebarGroup>\n <SidebarMenu>\n {data.navMain.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild>\n <a href={item.url} className=\"font-medium\">\n {item.title}\n </a>\n </SidebarMenuButton>\n {item.items?.length ? (\n <SidebarMenuSub>\n {item.items.map((item) => (\n <SidebarMenuSubItem key={item.title}>\n <SidebarMenuSubButton asChild isActive={item.isActive}>\n <a href={item.url}>{item.title}</a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n ) : null}\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n </SidebarContent>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,17 +19,20 @@
|
||||
{
|
||||
"path": "block/sidebar-06/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { GalleryVerticalEnd } from \"lucide-react\"\n\nimport { NavMain } from \"@/registry/default/block/sidebar-06/components/nav-main\"\nimport { SidebarOptInForm } from \"@/registry/default/block/sidebar-06/components/sidebar-opt-in-form\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n navMain: [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild>\n <a href=\"#\">\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n <div className=\"flex flex-col gap-0.5 leading-none\">\n <span className=\"font-semibold\">Documentation</span>\n <span className=\"\">v1.0.0</span>\n </div>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n <SidebarContent>\n <NavMain items={data.navMain} />\n </SidebarContent>\n <SidebarFooter>\n <div className=\"p-1\">\n <SidebarOptInForm />\n </div>\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-06/components/nav-main.tsx",
|
||||
"content": "\"use client\"\n\nimport { MoreHorizontal, type LucideIcon } from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarGroup,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavMain({\n items,\n}: {\n items: {\n title: string\n url: string\n icon?: LucideIcon\n isActive?: boolean\n items?: {\n title: string\n url: string\n }[]\n }[]\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarGroup>\n <SidebarMenu>\n {items.map((item) => (\n <DropdownMenu key={item.title}>\n <SidebarMenuItem>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\">\n {item.title} <MoreHorizontal className=\"ml-auto\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n {item.items?.length ? (\n <DropdownMenuContent\n side={isMobile ? \"bottom\" : \"right\"}\n align={isMobile ? \"end\" : \"start\"}\n className=\"min-w-56 rounded-lg\"\n >\n {item.items.map((item) => (\n <DropdownMenuItem asChild key={item.title}>\n <a href={item.url}>{item.title}</a>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n ) : null}\n </SidebarMenuItem>\n </DropdownMenu>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-06/components/sidebar-opt-in-form.tsx",
|
||||
"content": "import { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { SidebarInput } from \"@/registry/default/ui/sidebar\"\n\nexport function SidebarOptInForm() {\n return (\n <Card className=\"shadow-none\">\n <form>\n <CardHeader className=\"p-4 pb-0\">\n <CardTitle className=\"text-sm\">Subscribe to our newsletter</CardTitle>\n <CardDescription>\n Opt-in to receive updates and news about the sidebar.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-2.5 p-4\">\n <SidebarInput type=\"email\" placeholder=\"Email\" />\n <Button\n className=\"w-full bg-sidebar-primary text-sidebar-primary-foreground shadow-none\"\n size=\"sm\"\n >\n Subscribe\n </Button>\n </CardContent>\n </form>\n </Card>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,27 +20,32 @@
|
||||
{
|
||||
"path": "block/sidebar-07/components/app-sidebar.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n AudioWaveform,\n BookOpen,\n Bot,\n Command,\n Frame,\n GalleryVerticalEnd,\n Map,\n PieChart,\n Settings2,\n SquareTerminal,\n} from \"lucide-react\"\n\nimport { NavMain } from \"@/registry/default/block/sidebar-07/components/nav-main\"\nimport { NavProjects } from \"@/registry/default/block/sidebar-07/components/nav-projects\"\nimport { NavUser } from \"@/registry/default/block/sidebar-07/components/nav-user\"\nimport { TeamSwitcher } from \"@/registry/default/block/sidebar-07/components/team-switcher\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n teams: [\n {\n name: \"Acme Inc\",\n logo: GalleryVerticalEnd,\n plan: \"Enterprise\",\n },\n {\n name: \"Acme Corp.\",\n logo: AudioWaveform,\n plan: \"Startup\",\n },\n {\n name: \"Evil Corp.\",\n logo: Command,\n plan: \"Free\",\n },\n ],\n navMain: [\n {\n title: \"Playground\",\n url: \"#\",\n icon: SquareTerminal,\n isActive: true,\n items: [\n {\n title: \"History\",\n url: \"#\",\n },\n {\n title: \"Starred\",\n url: \"#\",\n },\n {\n title: \"Settings\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Models\",\n url: \"#\",\n icon: Bot,\n items: [\n {\n title: \"Genesis\",\n url: \"#\",\n },\n {\n title: \"Explorer\",\n url: \"#\",\n },\n {\n title: \"Quantum\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Documentation\",\n url: \"#\",\n icon: BookOpen,\n items: [\n {\n title: \"Introduction\",\n url: \"#\",\n },\n {\n title: \"Get Started\",\n url: \"#\",\n },\n {\n title: \"Tutorials\",\n url: \"#\",\n },\n {\n title: \"Changelog\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Settings\",\n url: \"#\",\n icon: Settings2,\n items: [\n {\n title: \"General\",\n url: \"#\",\n },\n {\n title: \"Team\",\n url: \"#\",\n },\n {\n title: \"Billing\",\n url: \"#\",\n },\n {\n title: \"Limits\",\n url: \"#\",\n },\n ],\n },\n ],\n projects: [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar collapsible=\"icon\" {...props}>\n <SidebarHeader>\n <TeamSwitcher teams={data.teams} />\n </SidebarHeader>\n <SidebarContent>\n <NavMain items={data.navMain} />\n <NavProjects projects={data.projects} />\n </SidebarContent>\n <SidebarFooter>\n <NavUser user={data.user} />\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-07/components/nav-main.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavMain({\n items,\n}: {\n items: {\n title: string\n url: string\n icon?: LucideIcon\n isActive?: boolean\n items?: {\n title: string\n url: string\n }[]\n }[]\n}) {\n return (\n <SidebarGroup>\n <SidebarGroupLabel>Platform</SidebarGroupLabel>\n <SidebarMenu>\n {items.map((item) => (\n <Collapsible\n key={item.title}\n asChild\n defaultOpen={item.isActive}\n className=\"group/collapsible\"\n >\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton tooltip={item.title}>\n {item.icon && <item.icon />}\n <span>{item.title}</span>\n <ChevronRight className=\"ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.items?.map((subItem) => (\n <SidebarMenuSubItem key={subItem.title}>\n <SidebarMenuSubButton asChild>\n <a href={subItem.url}>\n <span>{subItem.title}</span>\n </a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-07/components/nav-projects.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n Folder,\n Forward,\n MoreHorizontal,\n Trash2,\n type LucideIcon,\n} from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavProjects({\n projects,\n}: {\n projects: {\n name: string\n url: string\n icon: LucideIcon\n }[]\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarGroup className=\"group-data-[collapsible=icon]:hidden\">\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarMenu>\n {projects.map((item) => (\n <SidebarMenuItem key={item.name}>\n <SidebarMenuButton asChild>\n <a href={item.url}>\n <item.icon />\n <span>{item.name}</span>\n </a>\n </SidebarMenuButton>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuAction showOnHover>\n <MoreHorizontal />\n <span className=\"sr-only\">More</span>\n </SidebarMenuAction>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-48 rounded-lg\"\n side={isMobile ? \"bottom\" : \"right\"}\n align={isMobile ? \"end\" : \"start\"}\n >\n <DropdownMenuItem>\n <Folder className=\"text-muted-foreground\" />\n <span>View Project</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Forward className=\"text-muted-foreground\" />\n <span>Share Project</span>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <Trash2 className=\"text-muted-foreground\" />\n <span>Delete Project</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n ))}\n <SidebarMenuItem>\n <SidebarMenuButton className=\"text-sidebar-foreground/70\">\n <MoreHorizontal className=\"text-sidebar-foreground/70\" />\n <span>More</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-07/components/nav-user.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n BadgeCheck,\n Bell,\n ChevronsUpDown,\n CreditCard,\n LogOut,\n Sparkles,\n} from \"lucide-react\"\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavUser({\n user,\n}: {\n user: {\n name: string\n email: string\n avatar: string\n }\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n side={isMobile ? \"bottom\" : \"right\"}\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"p-0 font-normal\">\n <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <Sparkles />\n Upgrade to Pro\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <BadgeCheck />\n Account\n </DropdownMenuItem>\n <DropdownMenuItem>\n <CreditCard />\n Billing\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Bell />\n Notifications\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <LogOut />\n Log out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-07/components/team-switcher.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronsUpDown, Plus } from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function TeamSwitcher({\n teams,\n}: {\n teams: {\n name: string\n logo: React.ElementType\n plan: string\n }[]\n}) {\n const { isMobile } = useSidebar()\n const [activeTeam, setActiveTeam] = React.useState(teams[0])\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n <activeTeam.logo className=\"size-4\" />\n </div>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">\n {activeTeam.name}\n </span>\n <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n </div>\n <ChevronsUpDown className=\"ml-auto\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n align=\"start\"\n side={isMobile ? \"bottom\" : \"right\"}\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"text-xs text-muted-foreground\">\n Teams\n </DropdownMenuLabel>\n {teams.map((team, index) => (\n <DropdownMenuItem\n key={team.name}\n onClick={() => setActiveTeam(team)}\n className=\"gap-2 p-2\"\n >\n <div className=\"flex size-6 items-center justify-center rounded-sm border\">\n <team.logo className=\"size-4 shrink-0\" />\n </div>\n {team.name}\n <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n </DropdownMenuItem>\n ))}\n <DropdownMenuSeparator />\n <DropdownMenuItem className=\"gap-2 p-2\">\n <div className=\"flex size-6 items-center justify-center rounded-md border bg-background\">\n <Plus className=\"size-4\" />\n </div>\n <div className=\"font-medium text-muted-foreground\">Add team</div>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,27 +20,32 @@
|
||||
{
|
||||
"path": "block/sidebar-08/components/app-sidebar.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n BookOpen,\n Bot,\n Command,\n Frame,\n LifeBuoy,\n Map,\n PieChart,\n Send,\n Settings2,\n SquareTerminal,\n} from \"lucide-react\"\n\nimport { NavMain } from \"@/registry/default/block/sidebar-08/components/nav-main\"\nimport { NavProjects } from \"@/registry/default/block/sidebar-08/components/nav-projects\"\nimport { NavSecondary } from \"@/registry/default/block/sidebar-08/components/nav-secondary\"\nimport { NavUser } from \"@/registry/default/block/sidebar-08/components/nav-user\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n} from \"@/registry/default/ui/sidebar\"\n\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n navMain: [\n {\n title: \"Playground\",\n url: \"#\",\n icon: SquareTerminal,\n isActive: true,\n items: [\n {\n title: \"History\",\n url: \"#\",\n },\n {\n title: \"Starred\",\n url: \"#\",\n },\n {\n title: \"Settings\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Models\",\n url: \"#\",\n icon: Bot,\n items: [\n {\n title: \"Genesis\",\n url: \"#\",\n },\n {\n title: \"Explorer\",\n url: \"#\",\n },\n {\n title: \"Quantum\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Documentation\",\n url: \"#\",\n icon: BookOpen,\n items: [\n {\n title: \"Introduction\",\n url: \"#\",\n },\n {\n title: \"Get Started\",\n url: \"#\",\n },\n {\n title: \"Tutorials\",\n url: \"#\",\n },\n {\n title: \"Changelog\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Settings\",\n url: \"#\",\n icon: Settings2,\n items: [\n {\n title: \"General\",\n url: \"#\",\n },\n {\n title: \"Team\",\n url: \"#\",\n },\n {\n title: \"Billing\",\n url: \"#\",\n },\n {\n title: \"Limits\",\n url: \"#\",\n },\n ],\n },\n ],\n navSecondary: [\n {\n title: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n },\n {\n title: \"Feedback\",\n url: \"#\",\n icon: Send,\n },\n ],\n projects: [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar variant=\"inset\" {...props}>\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild>\n <a href=\"#\">\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground\">\n <Command className=\"size-4\" />\n </div>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">Acme Inc</span>\n <span className=\"truncate text-xs\">Enterprise</span>\n </div>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n <SidebarContent>\n <NavMain items={data.navMain} />\n <NavProjects projects={data.projects} />\n <NavSecondary items={data.navSecondary} className=\"mt-auto\" />\n </SidebarContent>\n <SidebarFooter>\n <NavUser user={data.user} />\n </SidebarFooter>\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-08/components/nav-main.tsx",
|
||||
"content": "\"use client\"\n\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavMain({\n items,\n}: {\n items: {\n title: string\n url: string\n icon: LucideIcon\n isActive?: boolean\n items?: {\n title: string\n url: string\n }[]\n }[]\n}) {\n return (\n <SidebarGroup>\n <SidebarGroupLabel>Platform</SidebarGroupLabel>\n <SidebarMenu>\n {items.map((item) => (\n <Collapsible key={item.title} asChild defaultOpen={item.isActive}>\n <SidebarMenuItem>\n <SidebarMenuButton asChild tooltip={item.title}>\n <a href={item.url}>\n <item.icon />\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n {item.items?.length ? (\n <>\n <CollapsibleTrigger asChild>\n <SidebarMenuAction className=\"data-[state=open]:rotate-90\">\n <ChevronRight />\n <span className=\"sr-only\">Toggle</span>\n </SidebarMenuAction>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.items?.map((subItem) => (\n <SidebarMenuSubItem key={subItem.title}>\n <SidebarMenuSubButton asChild>\n <a href={subItem.url}>\n <span>{subItem.title}</span>\n </a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </>\n ) : null}\n </SidebarMenuItem>\n </Collapsible>\n ))}\n </SidebarMenu>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-08/components/nav-projects.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n Folder,\n MoreHorizontal,\n Share,\n Trash2,\n type LucideIcon,\n} from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavProjects({\n projects,\n}: {\n projects: {\n name: string\n url: string\n icon: LucideIcon\n }[]\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarGroup className=\"group-data-[collapsible=icon]:hidden\">\n <SidebarGroupLabel>Projects</SidebarGroupLabel>\n <SidebarMenu>\n {projects.map((item) => (\n <SidebarMenuItem key={item.name}>\n <SidebarMenuButton asChild>\n <a href={item.url}>\n <item.icon />\n <span>{item.name}</span>\n </a>\n </SidebarMenuButton>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuAction showOnHover>\n <MoreHorizontal />\n <span className=\"sr-only\">More</span>\n </SidebarMenuAction>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-48\"\n side={isMobile ? \"bottom\" : \"right\"}\n align={isMobile ? \"end\" : \"start\"}\n >\n <DropdownMenuItem>\n <Folder className=\"text-muted-foreground\" />\n <span>View Project</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Share className=\"text-muted-foreground\" />\n <span>Share Project</span>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <Trash2 className=\"text-muted-foreground\" />\n <span>Delete Project</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n ))}\n <SidebarMenuItem>\n <SidebarMenuButton>\n <MoreHorizontal />\n <span>More</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-08/components/nav-secondary.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { type LucideIcon } from \"lucide-react\"\n\nimport {\n SidebarGroup,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavSecondary({\n items,\n ...props\n}: {\n items: {\n title: string\n url: string\n icon: LucideIcon\n }[]\n} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {\n return (\n <SidebarGroup {...props}>\n <SidebarGroupContent>\n <SidebarMenu>\n {items.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild size=\"sm\">\n <a href={item.url}>\n <item.icon />\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-08/components/nav-user.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n BadgeCheck,\n Bell,\n ChevronsUpDown,\n CreditCard,\n LogOut,\n Sparkles,\n} from \"lucide-react\"\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavUser({\n user,\n}: {\n user: {\n name: string\n email: string\n avatar: string\n }\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n side={isMobile ? \"bottom\" : \"right\"}\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"p-0 font-normal\">\n <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <Sparkles />\n Upgrade to Pro\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <BadgeCheck />\n Account\n </DropdownMenuItem>\n <DropdownMenuItem>\n <CreditCard />\n Billing\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Bell />\n Notifications\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <LogOut />\n Log out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,7 +18,8 @@
|
||||
{
|
||||
"path": "block/sidebar-11/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { ChevronRight, File, Folder } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n changes: [\n {\n file: \"README.md\",\n state: \"M\",\n },\n {\n file: \"api/hello/route.ts\",\n state: \"U\",\n },\n {\n file: \"app/layout.tsx\",\n state: \"M\",\n },\n ],\n tree: [\n [\n \"app\",\n [\n \"api\",\n [\"hello\", [\"route.ts\"]],\n \"page.tsx\",\n \"layout.tsx\",\n [\"blog\", [\"page.tsx\"]],\n ],\n ],\n [\n \"components\",\n [\"ui\", \"button.tsx\", \"card.tsx\"],\n \"header.tsx\",\n \"footer.tsx\",\n ],\n [\"lib\", [\"util.ts\"]],\n [\"public\", \"favicon.ico\", \"vercel.svg\"],\n \".eslintrc.json\",\n \".gitignore\",\n \"next.config.js\",\n \"tailwind.config.js\",\n \"package.json\",\n \"README.md\",\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Changes</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {data.changes.map((item, index) => (\n <SidebarMenuItem key={index}>\n <SidebarMenuButton>\n <File />\n {item.file}\n </SidebarMenuButton>\n <SidebarMenuBadge>{item.state}</SidebarMenuBadge>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n <SidebarGroup>\n <SidebarGroupLabel>Files</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {data.tree.map((item, index) => (\n <Tree key={index} item={item} />\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarRail />\n </Sidebar>\n )\n}\n\nfunction Tree({ item }: { item: string | any[] }) {\n const [name, ...items] = Array.isArray(item) ? item : [item]\n\n if (!items.length) {\n return (\n <SidebarMenuButton\n isActive={name === \"button.tsx\"}\n className=\"data-[active=true]:bg-transparent\"\n >\n <File />\n {name}\n </SidebarMenuButton>\n )\n }\n\n return (\n <SidebarMenuItem>\n <Collapsible\n className=\"group/collapsible [&[data-state=open]>button>svg:first-child]:rotate-90\"\n defaultOpen={name === \"components\" || name === \"ui\"}\n >\n <CollapsibleTrigger asChild>\n <SidebarMenuButton>\n <ChevronRight className=\"transition-transform\" />\n <Folder />\n {name}\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {items.map((subItem, index) => (\n <Tree key={index} item={subItem} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuItem>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21,22 +21,26 @@
|
||||
{
|
||||
"path": "block/sidebar-12/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { Plus } from \"lucide-react\"\n\nimport { Calendars } from \"@/registry/default/block/sidebar-12/components/calendars\"\nimport { DatePicker } from \"@/registry/default/block/sidebar-12/components/date-picker\"\nimport { NavUser } from \"@/registry/default/block/sidebar-12/components/nav-user\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n SidebarSeparator,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n calendars: [\n {\n name: \"My Calendars\",\n items: [\"Personal\", \"Work\", \"Family\"],\n },\n {\n name: \"Favorites\",\n items: [\"Holidays\", \"Birthdays\"],\n },\n {\n name: \"Other\",\n items: [\"Travel\", \"Reminders\", \"Deadlines\"],\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarHeader className=\"h-16 border-b border-sidebar-border\">\n <NavUser user={data.user} />\n </SidebarHeader>\n <SidebarContent>\n <DatePicker />\n <SidebarSeparator className=\"mx-0\" />\n <Calendars calendars={data.calendars} />\n </SidebarContent>\n <SidebarFooter>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton>\n <Plus />\n <span>New Calendar</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-12/components/calendars.tsx",
|
||||
"content": "import * as React from \"react\"\nimport { Check, ChevronRight } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarSeparator,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function Calendars({\n calendars,\n}: {\n calendars: {\n name: string\n items: string[]\n }[]\n}) {\n return (\n <>\n {calendars.map((calendar, index) => (\n <React.Fragment key={calendar.name}>\n <SidebarGroup key={calendar.name} className=\"py-0\">\n <Collapsible\n defaultOpen={index === 0}\n className=\"group/collapsible\"\n >\n <SidebarGroupLabel\n asChild\n className=\"group/label w-full text-sm text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\"\n >\n <CollapsibleTrigger>\n {calendar.name}{\" \"}\n <ChevronRight className=\"ml-auto transition-transform group-data-[state=open]/collapsible:rotate-90\" />\n </CollapsibleTrigger>\n </SidebarGroupLabel>\n <CollapsibleContent>\n <SidebarGroupContent>\n <SidebarMenu>\n {calendar.items.map((item, index) => (\n <SidebarMenuItem key={item}>\n <SidebarMenuButton>\n <div\n data-active={index < 2}\n className=\"group/calendar-item flex aspect-square size-4 shrink-0 items-center justify-center rounded-sm border border-sidebar-border text-sidebar-primary-foreground data-[active=true]:border-sidebar-primary data-[active=true]:bg-sidebar-primary\"\n >\n <Check className=\"hidden size-3 group-data-[active=true]/calendar-item:block\" />\n </div>\n {item}\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </CollapsibleContent>\n </Collapsible>\n </SidebarGroup>\n <SidebarSeparator className=\"mx-0\" />\n </React.Fragment>\n ))}\n </>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-12/components/date-picker.tsx",
|
||||
"content": "import { Calendar } from \"@/registry/default/ui/calendar\"\nimport {\n SidebarGroup,\n SidebarGroupContent,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function DatePicker() {\n return (\n <SidebarGroup className=\"px-0\">\n <SidebarGroupContent>\n <Calendar className=\"[&_[role=gridcell].bg-accent]:bg-sidebar-primary [&_[role=gridcell].bg-accent]:text-sidebar-primary-foreground [&_[role=gridcell]]:w-[33px]\" />\n </SidebarGroupContent>\n </SidebarGroup>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"path": "block/sidebar-12/components/nav-user.tsx",
|
||||
"content": "\"use client\"\n\nimport {\n BadgeCheck,\n Bell,\n ChevronsUpDown,\n CreditCard,\n LogOut,\n Sparkles,\n} from \"lucide-react\"\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavUser({\n user,\n}: {\n user: {\n name: string\n email: string\n avatar: string\n }\n}) {\n const { isMobile } = useSidebar()\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n side={isMobile ? \"bottom\" : \"right\"}\n align=\"start\"\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"p-0 font-normal\">\n <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user.avatar} alt={user.name} />\n <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user.name}</span>\n <span className=\"truncate text-xs\">{user.email}</span>\n </div>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <Sparkles />\n Upgrade to Pro\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <BadgeCheck />\n Account\n </DropdownMenuItem>\n <DropdownMenuItem>\n <CreditCard />\n Billing\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Bell />\n Notifications\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <LogOut />\n Log out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,8 @@
|
||||
{
|
||||
"path": "block/sidebar-13/components/settings-dialog.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n Bell,\n Check,\n Globe,\n Home,\n Keyboard,\n Link,\n Lock,\n Menu,\n MessageCircle,\n Paintbrush,\n Settings,\n Video,\n} from \"lucide-react\"\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogTitle,\n DialogTrigger,\n} from \"@/registry/default/ui/dialog\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst data = {\n nav: [\n { name: \"Notifications\", icon: Bell },\n { name: \"Navigation\", icon: Menu },\n { name: \"Home\", icon: Home },\n { name: \"Appearance\", icon: Paintbrush },\n { name: \"Messages & media\", icon: MessageCircle },\n { name: \"Language & region\", icon: Globe },\n { name: \"Accessibility\", icon: Keyboard },\n { name: \"Mark as read\", icon: Check },\n { name: \"Audio & video\", icon: Video },\n { name: \"Connected accounts\", icon: Link },\n { name: \"Privacy & visibility\", icon: Lock },\n { name: \"Advanced\", icon: Settings },\n ],\n}\n\nexport function SettingsDialog() {\n const [open, setOpen] = React.useState(true)\n\n return (\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogTrigger asChild>\n <Button size=\"sm\">Open Dialog</Button>\n </DialogTrigger>\n <DialogContent className=\"overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]\">\n <DialogTitle className=\"sr-only\">Settings</DialogTitle>\n <DialogDescription className=\"sr-only\">\n Customize your settings here.\n </DialogDescription>\n <SidebarProvider className=\"items-start\">\n <Sidebar collapsible=\"none\" className=\"hidden md:flex\">\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupContent>\n <SidebarMenu>\n {data.nav.map((item) => (\n <SidebarMenuItem key={item.name}>\n <SidebarMenuButton\n asChild\n isActive={item.name === \"Messages & media\"}\n >\n <a href=\"#\">\n <item.icon />\n <span>{item.name}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n </Sidebar>\n <main className=\"flex h-[480px] flex-1 flex-col overflow-hidden\">\n <header className=\"flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12\">\n <div className=\"flex items-center gap-2 px-4\">\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem className=\"hidden md:block\">\n <BreadcrumbLink href=\"#\">Settings</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator className=\"hidden md:block\" />\n <BreadcrumbItem>\n <BreadcrumbPage>Messages & media</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n </div>\n </header>\n <div className=\"flex flex-1 flex-col gap-4 overflow-y-auto p-4 pt-0\">\n {Array.from({ length: 10 }).map((_, i) => (\n <div\n key={i}\n className=\"aspect-video max-w-3xl rounded-xl bg-muted/50\"\n />\n ))}\n </div>\n </main>\n </SidebarProvider>\n </DialogContent>\n </Dialog>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,7 +16,8 @@
|
||||
{
|
||||
"path": "block/sidebar-14/components/app-sidebar.tsx",
|
||||
"content": "import * as React from \"react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n navMain: [\n {\n title: \"Getting Started\",\n url: \"#\",\n items: [\n {\n title: \"Installation\",\n url: \"#\",\n },\n {\n title: \"Project Structure\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Building Your Application\",\n url: \"#\",\n items: [\n {\n title: \"Routing\",\n url: \"#\",\n },\n {\n title: \"Data Fetching\",\n url: \"#\",\n isActive: true,\n },\n {\n title: \"Rendering\",\n url: \"#\",\n },\n {\n title: \"Caching\",\n url: \"#\",\n },\n {\n title: \"Styling\",\n url: \"#\",\n },\n {\n title: \"Optimizing\",\n url: \"#\",\n },\n {\n title: \"Configuring\",\n url: \"#\",\n },\n {\n title: \"Testing\",\n url: \"#\",\n },\n {\n title: \"Authentication\",\n url: \"#\",\n },\n {\n title: \"Deploying\",\n url: \"#\",\n },\n {\n title: \"Upgrading\",\n url: \"#\",\n },\n {\n title: \"Examples\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"API Reference\",\n url: \"#\",\n items: [\n {\n title: \"Components\",\n url: \"#\",\n },\n {\n title: \"File Conventions\",\n url: \"#\",\n },\n {\n title: \"Functions\",\n url: \"#\",\n },\n {\n title: \"next.config.js Options\",\n url: \"#\",\n },\n {\n title: \"CLI\",\n url: \"#\",\n },\n {\n title: \"Edge Runtime\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Architecture\",\n url: \"#\",\n items: [\n {\n title: \"Accessibility\",\n url: \"#\",\n },\n {\n title: \"Fast Refresh\",\n url: \"#\",\n },\n {\n title: \"Next.js Compiler\",\n url: \"#\",\n },\n {\n title: \"Supported Browsers\",\n url: \"#\",\n },\n {\n title: \"Turbopack\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Community\",\n url: \"#\",\n items: [\n {\n title: \"Contribution Guide\",\n url: \"#\",\n },\n ],\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar {...props}>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Table of Contents</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {data.navMain.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild>\n <a href={item.url} className=\"font-medium\">\n {item.title}\n </a>\n </SidebarMenuButton>\n {item.items?.length ? (\n <SidebarMenuSub>\n {item.items.map((item) => (\n <SidebarMenuSubItem key={item.title}>\n <SidebarMenuSubButton\n asChild\n isActive={item.isActive}\n >\n <a href={item.url}>{item.title}</a>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n ))}\n </SidebarMenuSub>\n ) : null}\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user