mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
Compare commits
27 Commits
shadcn@2.1
...
shadcn-ui@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
729b9ec8ca | ||
|
|
a1bed464f3 | ||
|
|
805ed4120a | ||
|
|
600a593c87 | ||
|
|
500dbe2664 | ||
|
|
c577ee0666 | ||
|
|
d5bf0018fd | ||
|
|
fb36ca4159 | ||
|
|
824a35ada1 | ||
|
|
8d520c8d49 | ||
|
|
0873835339 | ||
|
|
4a0d4cfdb9 | ||
|
|
c4c5d8d419 | ||
|
|
9253682b87 | ||
|
|
c7cd16a637 | ||
|
|
eff1918d41 | ||
|
|
366d6b656b | ||
|
|
e489c5e08e | ||
|
|
d87003e0a4 | ||
|
|
a8633075f7 | ||
|
|
432d5e6e28 | ||
|
|
8f0c26f22a | ||
|
|
149b321c1b | ||
|
|
c1ae5a57cc | ||
|
|
b8ed303d8c | ||
|
|
13c97acf9f | ||
|
|
bed277c54d |
@@ -91,6 +91,42 @@ pnpm --filter=www dev
|
||||
pnpm --filter=shadcn-ui dev
|
||||
```
|
||||
|
||||
## Running the CLI Locally
|
||||
|
||||
To run the CLI locally, you can follow the workflow:
|
||||
|
||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||
|
||||
```bash
|
||||
pnpm www:dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm shadcn:dev
|
||||
```
|
||||
|
||||
3. In another terminal tab, test the CLI by running:
|
||||
|
||||
```bash
|
||||
pnpm shadcn
|
||||
```
|
||||
|
||||
To test the CLI in a specific app, use a command like:
|
||||
|
||||
```bash
|
||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
||||
```
|
||||
|
||||
4. To run the tests for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn test
|
||||
```
|
||||
|
||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
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 (
|
||||
|
||||
@@ -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,7 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
@@ -10,7 +10,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,32 +23,37 @@ interface ExamplesLayoutProps {
|
||||
|
||||
export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Check out some examples
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">Examples</PageHeaderHeading>
|
||||
<PageHeaderHeading>Build your component library</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Dashboard, cards, authentication. Some examples built using the
|
||||
components. Use this as a guide to build your own.
|
||||
Beautifully designed components that you can copy and paste into your
|
||||
apps. Made with Tailwind CSS. Open source.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs">Get Started</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/components">Components</Link>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={siteConfig.links.github}
|
||||
>
|
||||
GitHub
|
||||
</Link>
|
||||
</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
|
||||
fontSans.variable,
|
||||
fontMono.variable
|
||||
)}
|
||||
>
|
||||
<ThemeProvider
|
||||
@@ -91,6 +101,7 @@ 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">
|
||||
|
||||
@@ -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 ${item.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>
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ export function ExamplesNav({ className, ...props }: ExamplesNavProps) {
|
||||
<div className="relative">
|
||||
<ScrollArea className="max-w-[600px] lg:max-w-none">
|
||||
<div className={cn("mb-4 flex items-center", className)} {...props}>
|
||||
{examples.map((example, index) => (
|
||||
{examples.map((example) => (
|
||||
<Link
|
||||
href={example.href}
|
||||
key={example.href}
|
||||
|
||||
@@ -12,18 +12,18 @@ export function MainNav() {
|
||||
|
||||
return (
|
||||
<div className="mr-4 hidden md:flex">
|
||||
<Link href="/" className="mr-4 flex items-center space-x-2 lg:mr-6">
|
||||
<Link href="/" className="mr-4 flex items-center gap-2 lg:mr-6">
|
||||
<Icons.logo className="h-6 w-6" />
|
||||
<span className="hidden font-bold lg:inline-block">
|
||||
{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(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname === "/docs" ? "text-foreground" : "text-foreground/60"
|
||||
pathname === "/docs" ? "text-foreground" : "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Docs
|
||||
@@ -35,7 +35,7 @@ export function MainNav() {
|
||||
pathname?.startsWith("/docs/components") &&
|
||||
!pathname?.startsWith("/docs/component/chart")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Components
|
||||
@@ -46,7 +46,7 @@ export function MainNav() {
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/blocks")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Blocks
|
||||
@@ -58,7 +58,7 @@ export function MainNav() {
|
||||
pathname?.startsWith("/docs/component/chart") ||
|
||||
pathname?.startsWith("/charts")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Charts
|
||||
@@ -69,29 +69,18 @@ export function MainNav() {
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/themes")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Themes
|
||||
</Link>
|
||||
<Link
|
||||
href="/examples"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/examples")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
)}
|
||||
>
|
||||
Examples
|
||||
</Link>
|
||||
<Link
|
||||
href="/colors"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/colors")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Colors
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import template from "lodash.template"
|
||||
import { Check, Copy, HelpCircle, Moon, Repeat, Sun } from "lucide-react"
|
||||
import template from "lodash/template"
|
||||
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",
|
||||
@@ -28,10 +32,6 @@ export const docsConfig: DocsConfig = {
|
||||
title: "Themes",
|
||||
href: "/themes",
|
||||
},
|
||||
{
|
||||
title: "Examples",
|
||||
href: "/examples",
|
||||
},
|
||||
{
|
||||
title: "Colors",
|
||||
href: "/colors",
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Every component recreated in Figma. With customizable props, typogr
|
||||
|
||||
## Paid
|
||||
|
||||
- [shadcn/ui kit](http://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
||||
- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
||||
|
||||
## Free
|
||||
|
||||
|
||||
@@ -16,22 +16,14 @@ Components are styled using Tailwind CSS. You need to install Tailwind CSS in yo
|
||||
Add the following dependencies to your project:
|
||||
|
||||
```bash
|
||||
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge
|
||||
```
|
||||
|
||||
### Add icon library
|
||||
|
||||
If you're using the `default` style, install `lucide-react`:
|
||||
|
||||
```bash
|
||||
npm install lucide-react
|
||||
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
|
||||
```
|
||||
|
||||
### Configure path aliases
|
||||
|
||||
I use the `@` alias. This is how I configure it in tsconfig.json:
|
||||
Configure the path aliases in your `tsconfig.json` file.
|
||||
|
||||
```json {3-6} title="tsconfig.json"
|
||||
```json {3-6} title="tsconfig.json" showLineNumbers
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
@@ -44,27 +36,16 @@ I use the `@` alias. This is how I configure it in tsconfig.json:
|
||||
|
||||
The `@` alias is a preference. You can use other aliases if you want.
|
||||
|
||||
**If you use a different alias such as ~, you'll need to update import statements when adding components.**
|
||||
|
||||
### Configure tailwind.config.js
|
||||
|
||||
Here's what my `tailwind.config.js` file looks like:
|
||||
|
||||
```js title="tailwind.config.js"
|
||||
const { fontFamily } = require("tailwindcss/defaultTheme")
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
@@ -106,23 +87,6 @@ module.exports = {
|
||||
md: `calc(var(--radius) - 2px)`,
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
@@ -142,67 +106,46 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,17 +154,14 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
@apply font-sans antialiased bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add a cn helper
|
||||
|
||||
I use a `cn` helper to make it easier to conditionally add Tailwind CSS classes. Here's how I define it in `lib/utils.ts`:
|
||||
|
||||
```ts title="lib/utils.ts"
|
||||
```ts title="lib/utils.ts" showLineNumbers
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
@@ -230,6 +170,34 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
```
|
||||
|
||||
### Create a `components.json` file
|
||||
|
||||
Create a `components.json` file in the root of your project.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
```
|
||||
|
||||
### That's it
|
||||
|
||||
You can now start adding components to your project.
|
||||
|
||||
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: {
|
||||
|
||||
@@ -67,12 +67,12 @@
|
||||
"geist": "^1.2.2",
|
||||
"input-otp": "^1.2.2",
|
||||
"jotai": "^2.1.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "0.359.0",
|
||||
"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,12 +85,12 @@
|
||||
"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": {
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
"@types/lodash.template": "^4.5.1",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/react": "^18.2.65",
|
||||
"@types/react-color": "^3.0.6",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\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": ""
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
{
|
||||
"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"
|
||||
"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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "ui/textarea.tsx",
|
||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface TextareaProps\n extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n ({ className, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n \"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background 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)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n",
|
||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Textarea = React.forwardRef<\n HTMLTextAreaElement,\n React.ComponentProps<\"textarea\">\n>(({ className, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n \"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background 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})\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n",
|
||||
"type": "registry:ui",
|
||||
"target": ""
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,8 @@
|
||||
{
|
||||
"path": "hooks/use-mobile.tsx",
|
||||
"content": "import * as React from \"react\"\n\nconst MOBILE_BREAKPOINT = 768\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n }\n mql.addEventListener(\"change\", onChange)\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n return () => mql.removeEventListener(\"change\", onChange)\n }, [])\n\n return !!isMobile\n}\n",
|
||||
"type": "registry:hook"
|
||||
"type": "registry:hook",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
{
|
||||
"path": "hooks/use-toast.ts",
|
||||
"content": "\"use client\"\n\n// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n ToastActionElement,\n ToastProps,\n} from \"@/registry/default/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n id: string\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionElement\n}\n\nconst actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER\n return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"]\n toast: ToasterToast\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"]\n toast: Partial<ToasterToast>\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n\ninterface State {\n toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId)\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n })\n }, TOAST_REMOVE_DELAY)\n\n toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n }\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t\n ),\n }\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId)\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id)\n })\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t\n ),\n }\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n }\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n }\n }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action)\n listeners.forEach((listener) => {\n listener(memoryState)\n })\n}\n\ntype Toast = Omit<ToasterToast, \"id\">\n\nfunction toast({ ...props }: Toast) {\n const id = genId()\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n })\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss()\n },\n },\n })\n\n return {\n id: id,\n dismiss,\n update,\n }\n}\n\nfunction useToast() {\n const [state, setState] = React.useState<State>(memoryState)\n\n React.useEffect(() => {\n listeners.push(setState)\n return () => {\n const index = listeners.indexOf(setState)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n }, [state])\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n }\n}\n\nexport { useToast, toast }\n",
|
||||
"type": "registry:hook"
|
||||
"type": "registry:hook",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
{
|
||||
"path": "lib/utils.ts",
|
||||
"content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
|
||||
"type": "registry:lib"
|
||||
"type": "registry:lib",
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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/new-york/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/new-york/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/new-york/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/new-york/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/new-york/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/new-york/ui/collapsible\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n} from \"@/registry/new-york/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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user