Compare commits

...

27 Commits

Author SHA1 Message Date
github-actions[bot]
729b9ec8ca chore(release): version packages (#5812)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-10 12:17:32 +04:00
Jens Astrup
a1bed464f3 chore(apps): Update lodash (#4397)
* chore(apps): Refactor usage of lodash.template to lodash to address security vulnerability

* chore(cli): Refactor usage of lodash.template to lodash to address security vulnerability

* deps: update lock

* chore: changesets

* style: fix format

* fix: import

* chore: build registry

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-12-09 12:33:28 +04:00
shadcn
805ed4120a feat(www): refactor examples page (#5818) 2024-11-13 16:01:14 +04:00
shadcn
600a593c87 docs(www): update manual installation (#5817) 2024-11-13 15:42:52 +04:00
Braden Corbold
500dbe2664 fix(shadcn) arrays and nested deeply nested spread (#5711)
* fix: tailwind config updater parser

* fix: remove quote around spread element

* fix: specify deepmerge option for array

* fix(shadcn): Nested and spread array elements

* add test case for boolean primitive

---------

Co-authored-by: matsuyoshi30 <sfbgwm30@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2024-11-13 15:19:21 +04:00
JEM
c577ee0666 docs(contributing): add CLI usage instructions (#5807)
Introduce a detailed workflow for running the CLI locally, ensuring contributors can easily test and develop enhancements in their local environment. This addition helps maintain consistency between CLI and component updates.
2024-11-13 15:17:11 +04:00
shadcn
d5bf0018fd fix(shadcn): handling of tsconfig aliases (#5813)
* fix(shadcn): handling of tsconfig aliases

* chore: add changeset
2024-11-13 15:15:22 +04:00
Tobbe Lundberg
fb36ca4159 fix(transform): Support aliases that are longer than one character (#5678)
* fix(transform): Support aliases that are longer than one character

* feat(shadcn): update handling of aliases

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-11-12 22:13:38 +04:00
JEM
824a35ada1 test(snapshots): update test snapshots for consistency (#5801)
Updated snapshot components to use `React.ComponentProps` instead of custom interfaces for `input` and `Dialog` components. This simplifies the code by leveraging built-in React types, ensuring consistency and reducing potential errors arising from custom definitions.
2024-11-12 12:54:31 +04:00
shadcn
8d520c8d49 fix(www): figma link (#5798) 2024-11-11 20:08:24 +04:00
Temkin Mengsitu
0873835339 fix: ensure block component name is copied in shadcn (#5790) 2024-11-11 12:10:04 +04:00
JEM
4a0d4cfdb9 refactor(components): remove redundant empty interfaces from Input, Command, and Textarea components (#5657)
* refactor(components): remove redundant empty interfaces from Input, Command, and Textarea components

Simplified props handling by removing redundant empty interfaces (`InputProps`, `CommandDialogProps`, `TextareaProps`). Directly extended native HTML attributes/lib for each component, enhancing code clarity and maintainability. This refactor ensures compliance with ESLint rules and streamlines future modifications without altering component functionality.

* updated the registry

* rerun build:registry with updated command

* Refactor input and textarea components to use `React.ComponentProps`

* update registry
2024-11-08 20:26:01 +04:00
shadcn
c4c5d8d419 feat(www): update next-themes 2024-11-08 15:18:32 +04:00
Wing Chung Ng
9253682b87 refactor(www): update ThemeProviderProps type extraction for next-themes v0.4.0 compatibility (#5701)
Since next-themes v0.4.0 no longer provides types in `dist`, replaced the import of `ThemeProviderProps` from `next-themes/dist/types` with a new type extraction using `React.ComponentProps`. This change ensures continued type safety and compatibility with the updated library version.

Co-authored-by: shadcn <m@shadcn.com>
2024-11-08 15:16:10 +04:00
shadcn
c7cd16a637 fix(www): interactive chart 2024-11-07 21:36:19 +04:00
shadcn
eff1918d41 fix: update file handling 2024-11-07 21:21:42 +04:00
shadcn
366d6b656b fix(www): file tracing 2024-11-07 21:05:44 +04:00
shadcn
e489c5e08e fix(www): file tracing 2024-11-07 20:54:26 +04:00
shadcn
d87003e0a4 fix(www): sidebar 2024-11-07 20:40:18 +04:00
shadcn
a8633075f7 fix(www): component source 2024-11-07 20:36:21 +04:00
shadcn
432d5e6e28 fix: code view for mdx 2024-11-07 17:45:34 +04:00
shadcn
8f0c26f22a feat(www): code for blocks (#5756)
* feat: update blocks

* fix: scrollbars

* fix: code viewer

* test(shadcn): fix
2024-11-07 17:09:41 +04:00
shadcn
149b321c1b fix: lint 2024-11-06 17:34:21 +04:00
shadcn
c1ae5a57cc feat(www): nav color 2024-11-06 17:29:53 +04:00
shadcn
b8ed303d8c Merge branch 'main' of github.com:shadcn-ui/ui 2024-11-06 17:19:41 +04:00
shadcn
13c97acf9f fix: font size 2024-11-06 17:16:21 +04:00
shadcn
bed277c54d feat( www): minor updates (#5749)
* feat(www): update mode switcher

* feat(www): update layout

* feat(www): update theme-color handling

* fix(www): docs pages
2024-11-06 17:03:28 +04:00
172 changed files with 8562 additions and 2413 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ import {
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
export function BlockCopyButton({
export function ChartCopyButton({
event,
name,
code,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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" : ""}

View File

@@ -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{" "}

View File

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

View File

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

View File

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

View File

@@ -11,3 +11,8 @@ export const siteConfig = {
}
export type SiteConfig = typeof siteConfig
export const META_THEME_COLORS = {
light: "#ffffff",
dark: "#09090b",
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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: [
{

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

View File

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

View File

@@ -2,6 +2,11 @@ import { createContentlayerPlugin } from "next-contentlayer2"
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
outputFileTracingIncludes: {
"/blocks/*": ["./registry/**/*"],
},
},
reactStrictMode: true,
swcMinify: true,
images: {

View File

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

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}

View File

@@ -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&apos;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": ""
}
]
}

View File

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

View File

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

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

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

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

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

View File

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

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

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

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

@@ -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": ""
}
]
}

View File

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