feat: update open in v0

This commit is contained in:
shadcn
2024-10-15 21:28:38 +04:00
parent 50c2f6045a
commit e461c02389
550 changed files with 6397 additions and 1921 deletions

View File

@@ -16,7 +16,8 @@ import {
export const iframeHeight = "800px"
export const teaser = "A simple sidebar with navigation grouped by section."
export const description =
"A simple sidebar with navigation grouped by section."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with collapsible sections."
export const description = "A sidebar with collapsible sections."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with submenus."
export const description = "A sidebar with submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A floating sidebar with submenus."
export const description = "A floating sidebar with submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with collapsible submenus."
export const description = "A sidebar with collapsible submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with submenus as dropdowns."
export const description = "A sidebar with submenus as dropdowns."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar that collapses to icons."
export const description = "A sidebar that collapses to icons."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "An inset sidebar with secondary navigation."
export const description = "An inset sidebar with secondary navigation."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "Collapsible nested sidebars"
export const description = "Collapsible nested sidebars"
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar in a popover."
export const description = "A sidebar in a popover."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with a collapsible file tree."
export const description = "A sidebar with a collapsible file tree."
export default function Page() {
return (

View File

@@ -14,7 +14,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with a calendar."
export const description = "A sidebar with a calendar."
export default function Page() {
return (

View File

@@ -2,7 +2,7 @@ import { SettingsDialog } from "@/registry/default/block/sidebar-13/components/s
export const iframeHeight = "800px"
export const teaser = "A sidebar in a dialog."
export const description = "A sidebar in a dialog."
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar on the right."
export const description = "A sidebar on the right."
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A left and right sidebar."
export const description = "A left and right sidebar."
export default function Page() {
return (

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
import { LoginForm } from "@/registry/new-york/block/login-01/components/login-form"
export const description = "A simple login form."
export const iframeHeight = "870px"
export const containerClassName = "w-full h-full"

View File

@@ -16,7 +16,8 @@ import {
export const iframeHeight = "800px"
export const teaser = "A simple sidebar with navigation grouped by section."
export const description =
"A simple sidebar with navigation grouped by section."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with collapsible sections."
export const description = "A sidebar with collapsible sections."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with submenus."
export const description = "A sidebar with submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A floating sidebar with submenus."
export const description = "A floating sidebar with submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with collapsible submenus."
export const description = "A sidebar with collapsible submenus."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with submenus as dropdowns."
export const description = "A sidebar with submenus as dropdowns."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar that collapses to icons."
export const description = "A sidebar that collapses to icons."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "An inset sidebar with secondary navigation."
export const description = "An inset sidebar with secondary navigation."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "Collapsible nested sidebars."
export const description = "Collapsible nested sidebars."
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar in a popover."
export const description = "A sidebar in a popover."
export default function Page() {
return (

View File

@@ -16,7 +16,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with a collapsible file tree."
export const description = "A sidebar with a collapsible file tree."
export default function Page() {
return (

View File

@@ -14,7 +14,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar with a calendar."
export const description = "A sidebar with a calendar."
export default function Page() {
return (

View File

@@ -2,7 +2,7 @@ import { SettingsDialog } from "@/registry/new-york/block/sidebar-13/components/
export const iframeHeight = "800px"
export const teaser = "A sidebar in a dialog."
export const description = "A sidebar in a dialog."
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A sidebar on the right."
export const description = "A sidebar on the right."
export default function Page() {
return (

View File

@@ -15,7 +15,7 @@ import {
export const iframeHeight = "800px"
export const teaser = "A left and right sidebar."
export const description = "A left and right sidebar."
export default function Page() {
return (

View File

@@ -2,28 +2,69 @@
import { track } from "@vercel/analytics/server"
import { capitalCase } from "change-case"
import { z } from "zod"
import { Style } from "@/registry/registry-styles"
import { registryEntrySchema, registryItemTypeSchema } from "@/registry/schema"
async function getRegistryItem(name: string, style: Style["name"]) {
const registryURL = new URL(
`${process.env.NEXT_PUBLIC_APP_URL}/r/styles/${style}/${name}.json`
)
const response = await fetch(registryURL)
if (!response.ok) {
return null
}
const data = await response.json()
const result = registryEntrySchema
.extend({
files: z.array(
z.object({
path: z.string(),
content: z.string().optional(),
type: registryItemTypeSchema,
target: z.string().optional(),
})
),
})
.safeParse(data)
if (!result.success) {
console.error(result.error)
return null
}
return result.data
}
export async function editInV0({
name,
title,
description,
style,
code,
url,
}: {
name: string
title?: string
description: string
style: string
code: string
style: Style["name"]
url: string
}) {
style = style ?? "new-york"
try {
title =
title ??
capitalCase(
name.replace(/\d+/g, "").replace("-demo", "").replace("-", " ")
)
const registryItem = await getRegistryItem(name, style)
if (!registryItem) {
return { error: "Something went wrong. Please try again later." }
}
const title = capitalCase(
registryItem.name
.replace(/\d+/g, "")
.replace("-demo", "")
.replace("-", " ")
)
const description = registryItem.description ?? title
await track("edit_in_v0", {
name,
@@ -33,12 +74,8 @@ export async function editInV0({
url,
})
// Replace "use client" in the code.
// v0 will handle this for us.
// code = code.replace(`"use client"`, "")
// Remove export const description = "..."
code = code.replace(/export const description =\s*".*";?/, "")
// TODO: support multiple files.
let code = registryItem.files?.[0]?.content
const payload = {
title,
@@ -54,6 +91,8 @@ export async function editInV0({
},
}
console.log(payload)
const response = await fetch(`${process.env.V0_URL}/chat/api/open-in-v0`, {
method: "POST",
body: JSON.stringify(payload),

View File

@@ -1,30 +1,27 @@
import * as React from "react"
import { unstable_cache } from "next/cache"
import { getAllBlockIds } from "@/lib/blocks"
import { THEMES } from "@/lib/themes"
import { BlockDisplay } from "@/components/block-display"
import { ThemesSwitcher } from "@/components/themes-selector"
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"])
export default async function BlocksPage() {
const blocks = (await getAllBlockIds()).filter(
(name) =>
!name.startsWith("chart-") &&
!name.startsWith("sidebar-01") &&
!name.startsWith("login-01")
)
// These themes are not compatible with the blocks yet.
const themes = THEMES.filter(
(theme) => !["default-daylight", "default-midnight"].includes(theme.id)
)
const blocks = await getBlocks()
return (
<div className="gap-3 md:flex md:flex-row-reverse md:items-start">
<ThemesSwitcher
themes={themes}
className="fixed inset-x-0 bottom-0 z-40 mt-12 flex bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 lg:sticky lg:bottom-auto lg:top-20"
/>
<div className="grid flex-1 gap-24 lg:gap-48">
{blocks.map((name, index) => (
<BlockDisplay key={`${name}-${index}`} name={name} />
<React.Suspense key={`${name}-${index}`}>
<BlockDisplay name={name} />
</React.Suspense>
))}
</div>
</div>

View File

@@ -6,7 +6,6 @@ import { getAllBlockIds, getBlock } from "@/lib/blocks"
import { absoluteUrl, cn } from "@/lib/utils"
import { BlockChunk } from "@/components/block-chunk"
import { BlockWrapper } from "@/components/block-wrapper"
import { ThemesStyle } from "@/components/themes-styles"
import { Style, styles } from "@/registry/registry-styles"
import "@/styles/mdx.css"
@@ -26,12 +25,15 @@ export async function generateMetadata({
return {}
}
const title = block.name
const description = block.description
return {
title: block.name,
description: block.description,
title,
description,
openGraph: {
title: block.name,
description: block.description,
title,
description,
type: "article",
url: absoluteUrl(`/blocks/${block.name}`),
images: [
@@ -45,8 +47,8 @@ export async function generateMetadata({
},
twitter: {
card: "summary_large_image",
title: block.name,
description: block.description,
title,
description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},

View File

@@ -46,15 +46,7 @@ export function BlockChunk({
name={chunk.name}
code={chunk.code}
/>
<V0Button
size="icon"
block={{
name: chunk.name,
description: chunk.description || "",
code: chunk.code,
style: block.style,
}}
/>
<V0Button size="icon" name={chunk.name} />
</div>
</div>
)}

View File

@@ -1,29 +1,32 @@
import { unstable_cache } from "next/cache"
import { getBlock } from "@/lib/blocks"
import { BlockPreview } from "@/components/block-preview"
import { styles } from "@/registry/registry-styles"
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"]
)
export async function BlockDisplay({ name }: { name: string }) {
const blocks = await Promise.all(
styles.map(async (style) => {
const block = await getBlock(name, style.name)
const hasLiftMode = block?.chunks ? block?.chunks?.length > 0 : false
const block = await getBlockByName(name)
// Cannot (and don't need to) pass to the client.
delete block?.component
delete block?.chunks
return {
...block,
hasLiftMode,
}
})
)
if (!blocks?.length) {
if (!block) {
return null
}
return blocks.map((block) => (
<BlockPreview key={`${block.style}-${block.name}`} block={block} />
))
return <BlockPreview key={block.name} block={block} />
}

View File

@@ -3,37 +3,24 @@
import * as React from "react"
import { ImperativePanelHandle } from "react-resizable-panels"
import { cn } from "@/lib/utils"
import { useConfig } from "@/hooks/use-config"
import { useLiftMode } from "@/hooks/use-lift-mode"
import { BlockToolbar } from "@/components/block-toolbar"
import { Icons } from "@/components/icons"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/new-york/ui/resizable"
import { Tabs, TabsContent } from "@/registry/new-york/ui/tabs"
import { Block } from "@/registry/schema"
import { type Block } from "@/registry/schema"
export function BlockPreview({
block,
}: {
block: Block & { hasLiftMode: boolean }
block: Pick<Block, "name" | "style" | "description" | "container">
}) {
const [config] = useConfig()
const { isLiftMode } = useLiftMode(block.name)
const [isLoading, setIsLoading] = React.useState(true)
const ref = React.useRef<ImperativePanelHandle>(null)
if (config.style !== block.style) {
return null
}
return (
<Tabs
<div
id={block.name}
defaultValue="preview"
className="relative grid w-full scroll-m-20 gap-4"
style={
{
@@ -42,52 +29,22 @@ export function BlockPreview({
}
>
<BlockToolbar block={block} resizablePanelRef={ref} />
<TabsContent
value="preview"
className="relative after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-lg after:bg-muted"
>
<ResizablePanelGroup direction="horizontal" className="relative z-10">
<ResizablePanel
ref={ref}
className={cn(
"relative rounded-lg border bg-background",
isLiftMode ? "border-border/50" : "border-border"
)}
defaultSize={100}
minSize={30}
>
{isLoading ? (
<div className="absolute inset-0 z-10 flex h-[--container-height] w-full items-center justify-center gap-2 bg-background text-sm text-muted-foreground">
<Icons.spinner className="h-4 w-4 animate-spin" />
Loading...
</div>
) : null}
<iframe
src={`/blocks/${block.style}/${block.name}`}
height={block.container?.height ?? 450}
className="chunk-mode relative z-20 w-full bg-background"
onLoad={() => {
setIsLoading(false)
}}
allowTransparency
/>
</ResizablePanel>
<ResizableHandle
className={cn(
"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",
isLiftMode && "invisible"
)}
<ResizablePanelGroup direction="horizontal" className="relative z-10">
<ResizablePanel
ref={ref}
className="relative rounded-lg border bg-background"
defaultSize={100}
minSize={30}
>
<iframe
src={`/blocks/${block.style}/${block.name}`}
height={block.container?.height ?? 450}
className="chunk-mode relative z-20 w-full bg-background"
/>
<ResizablePanel defaultSize={0} minSize={0} />
</ResizablePanelGroup>
</TabsContent>
<TabsContent value="code">
<div
data-rehype-pretty-code-fragment
dangerouslySetInnerHTML={{ __html: block.highlightedCode }}
className="w-full overflow-hidden rounded-md [&_pre]:my-0 [&_pre]:h-[--container-height] [&_pre]:overflow-auto [&_pre]:whitespace-break-spaces [&_pre]:p-6 [&_pre]:font-mono [&_pre]:text-sm [&_pre]:leading-relaxed"
/>
</TabsContent>
</Tabs>
</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,25 +1,21 @@
"use client"
import * as React from "react"
import { CircleHelp, Monitor, Smartphone, Tablet } from "lucide-react"
import Link from "next/link"
import {
Check,
Fullscreen,
Monitor,
Smartphone,
Tablet,
Terminal,
} from "lucide-react"
import { ImperativePanelHandle } from "react-resizable-panels"
import { trackEvent } from "@/lib/events"
import { cn } from "@/lib/utils"
import { useLiftMode } from "@/hooks/use-lift-mode"
import { BlockCopyButton } from "@/components/block-copy-button"
import { StyleSwitcher } from "@/components/style-switcher"
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { V0Button } from "@/components/v0-button"
import { Badge } from "@/registry/new-york/ui/badge"
import { Label } from "@/registry/new-york/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { Button } from "@/registry/new-york/ui/button"
import { Separator } from "@/registry/new-york/ui/separator"
import { Switch } from "@/registry/new-york/ui/switch"
import { TabsList, TabsTrigger } from "@/registry/new-york/ui/tabs"
import {
ToggleGroup,
ToggleGroupItem,
@@ -30,172 +26,85 @@ export function BlockToolbar({
block,
resizablePanelRef,
}: {
block: Block & { hasLiftMode: boolean }
block: Pick<Block, "name" | "style" | "description" | "container">
resizablePanelRef: React.RefObject<ImperativePanelHandle>
}) {
const { isLiftMode, toggleLiftMode } = useLiftMode(block.name)
const { copyToClipboard, isCopied } = useCopyToClipboard()
return (
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<TabsList className="hidden h-7 rounded-md p-0 px-[calc(theme(spacing.1)_-_2px)] py-[theme(spacing.1)] sm:flex">
<TabsTrigger
value="preview"
className="h-[1.45rem] rounded-sm px-2 text-xs"
disabled={isLiftMode}
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
className="h-[1.45rem] rounded-sm px-2 text-xs"
disabled={isLiftMode}
>
Code
</TabsTrigger>
</TabsList>
<Button asChild variant="link">
<a href={`#${block.name}`}>{block.description}</a>
</Button>
<div className="ml-auto flex items-center gap-2 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" />
<StyleSwitcher
className="h-[calc(theme(spacing.7)_-_1px)] dark:h-7"
disabled={isLiftMode}
/>
<Popover>
<PopoverTrigger
disabled={isLiftMode}
className="hidden text-muted-foreground hover:text-foreground disabled:opacity-50 sm:flex"
>
<CircleHelp className="h-3.5 w-3.5" />
<span className="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="top"
sideOffset={20}
className="space-y-3 rounded-[0.5rem] text-sm"
>
<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 inputs. It also uses shadows on cards and
buttons.
</p>
</PopoverContent>
</Popover>
<div className="hidden items-center gap-2 sm:flex">
<Separator
orientation="vertical"
className="mx-2 mr-0 hidden h-4 md:flex"
/>
<div className="flex items-center gap-2">
<a href={`#${block.name}`}>
<Badge
variant="secondary"
className={cn("bg-transparent", isLiftMode && "opacity-50")}
>
{block.name}
</Badge>
</a>
</div>
</div>
</div>
{block.code && (
<div className="ml-auto flex items-center gap-2 md:pr-[14px]">
{block.hasLiftMode && (
<>
<div className="hidden h-7 items-center justify-between gap-2 md:flex">
<Label htmlFor={`lift-mode-${block.name}`} className="text-xs">
Lift Mode
</Label>
<Switch
id={`lift-mode-${block.name}`}
checked={isLiftMode}
onCheckedChange={(value) => {
resizablePanelRef.current?.resize(100)
toggleLiftMode(block.name)
if (value) {
trackEvent({
name: "enable_lift_mode",
properties: {
name: block.name,
},
})
}
}}
/>
</div>
<Separator
orientation="vertical"
className="mx-2 hidden h-4 lg:inline-flex"
/>
</>
)}
<div className="hidden h-[28px] items-center gap-1.5 rounded-md border p-[2px] shadow-sm md:flex">
<ToggleGroup
disabled={isLiftMode}
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"
>
<Monitor className="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="60"
className="h-[22px] w-[22px] rounded-sm p-0"
>
<Tablet className="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="30"
className="h-[22px] w-[22px] rounded-sm p-0"
>
<Smartphone className="h-3.5 w-3.5" />
</ToggleGroupItem>
</ToggleGroup>
</div>
<Separator
orientation="vertical"
className="mx-2 hidden h-4 md:flex"
/>
<BlockCopyButton
event="copy_block_code"
name={block.name}
code={block.code}
disabled={isLiftMode}
/>
<V0Button
className="hidden md:flex"
id={`v0-button-${block.name}`}
disabled={
isLiftMode || ["login-01", "sidebar-01"].includes(block.name)
}
block={{
name: block.name,
description: block.description || "Edit in v0",
code: block.code,
style: block.style,
<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={block.name}
/>
</div>
</div>
)
}

View File

@@ -92,12 +92,7 @@ ${Object.entries(themesConfig?.activeTheme.cssVars.dark || {})
/>
<V0Button
id={`v0-button-${chart.name}`}
block={{
name: chart.name,
description: chart.description || "Edit in v0",
code: chart.code,
style: "default",
}}
name={chart.name}
className="h-7"
/>
</div>

View File

@@ -116,16 +116,7 @@ export function ComponentPreview({
<div className="flex items-center justify-between p-4">
<StyleSwitcher />
<div className="flex items-center gap-2">
{config.style === "default" && description ? (
<V0Button
block={{
code: codeString,
name,
style: config.style,
description,
}}
/>
) : null}
{description ? <V0Button name={name} /> : null}
<CopyButton
value={codeString}
variant="outline"

View File

@@ -13,7 +13,7 @@ export const Icons = {
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
strokeWidth="32"
/>
<line
x1="192"
@@ -24,7 +24,7 @@ export const Icons = {
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
strokeWidth="32"
/>
</svg>
),

View File

@@ -14,7 +14,6 @@ import {
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
import { Style } from "@/registry/registry-styles"
import { Block } from "@/registry/schema"
type Size = "default" | "icon"
@@ -48,13 +47,13 @@ function V0Tooltip({
}
export function V0Button({
block,
name,
size = "default",
disabled,
className,
...props
}: {
block: Pick<Block, "name" | "description" | "code" | "style">
name: string
size?: Size
} & ButtonProps) {
const [url, setUrl] = React.useState("https://ui.shadcn.com")
@@ -63,51 +62,12 @@ export function V0Button({
setUrl(window.location.href)
}, [])
if (block.style === "new-york") {
return (
<V0Tooltip size={size} style={block.style}>
<Button
aria-label="Open in v0"
className={cn(
"z-50 h-[calc(theme(spacing.7)_-_1px)] gap-1 rounded-[6px] bg-black px-3 text-xs text-white hover:bg-black hover:text-white dark:bg-white dark:text-black",
size === "icon" && "h-7 w-7 p-0",
className
)}
onClick={() => {
toast("New York not available.", {
description: (
<div className="flex items-center">
Only the Default style is available in{" "}
<V0Logo className="ml-1 text-foreground" aria-label="v0" />.
</div>
),
})
}}
disabled={
block.style === "new-york" && size === "icon" ? true : disabled
}
{...props}
>
{size === "icon" ? (
<V0Logo className="h-4 w-4" />
) : (
<>
Open in <V0Logo />
</>
)}
</Button>
</V0Tooltip>
)
}
return (
<form
action={async () => {
try {
const result = await editInV0({
name: block.name,
description: block.description || "",
code: block.code,
style: block.style,
name,
url,
})
@@ -142,7 +102,7 @@ function Form({
size = "default",
className,
...props
}: Omit<React.ComponentProps<typeof V0Button>, "block">) {
}: Omit<React.ComponentProps<typeof V0Button>, "name">) {
const { pending } = useFormStatus()
return (

View File

@@ -11,7 +11,7 @@ import { highlightCode } from "@/lib/highlight-code"
import { Style } from "@/registry/registry-styles"
import { BlockChunk, blockSchema, registryEntrySchema } from "@/registry/schema"
const DEFAULT_BLOCKS_STYLE = "default" satisfies Style["name"]
const DEFAULT_BLOCKS_STYLE = "new-york" satisfies Style["name"]
const project = new Project({
compilerOptions: {},
@@ -32,44 +32,55 @@ export async function getBlock(
const content = await _getBlockContent(name, style)
const chunks = await Promise.all(
entry.chunks?.map(async (chunk: BlockChunk) => {
const code = await readFile(chunk.file)
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,
})
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()
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/"),
}
})
)
: []
return {
...chunk,
code: sourceFile
.getText()
.replaceAll(`@/registry/${style}/`, "@/components/"),
}
})
)
return blockSchema.parse({
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) {

View File

@@ -18,6 +18,7 @@
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache"
},
"dependencies": {
"@emotion/is-prop-valid": "^1.3.1",
"@faker-js/faker": "^8.2.0",
"@hookform/resolvers": "^3.1.0",
"@radix-ui/react-accessible-icon": "^1.0.3",

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,15 @@
{
"name": "accordion-demo",
"type": "registry:example",
"registryDependencies": [
"accordion"
],
"files": [
{
"path": "example/accordion-demo.tsx",
"content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/registry/default/ui/accordion\"\n\nexport default function AccordionDemo() {\n return (\n <Accordion type=\"single\" collapsible className=\"w-full\">\n <AccordionItem value=\"item-1\">\n <AccordionTrigger>Is it accessible?</AccordionTrigger>\n <AccordionContent>\n Yes. It adheres to the WAI-ARIA design pattern.\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-2\">\n <AccordionTrigger>Is it styled?</AccordionTrigger>\n <AccordionContent>\n Yes. It comes with default styles that matches the other\n components&apos; aesthetic.\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-3\">\n <AccordionTrigger>Is it animated?</AccordionTrigger>\n <AccordionContent>\n Yes. It&apos;s animated by default, but you can disable it if you\n prefer.\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "alert-demo",
"type": "registry:example",
"registryDependencies": [
"alert"
],
"files": [
{
"path": "example/alert-demo.tsx",
"content": "import { Terminal } from \"lucide-react\"\n\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n} from \"@/registry/default/ui/alert\"\n\nexport default function AlertDemo() {\n return (\n <Alert>\n <Terminal className=\"h-4 w-4\" />\n <AlertTitle>Heads up!</AlertTitle>\n <AlertDescription>\n You can add components to your app using the cli.\n </AlertDescription>\n </Alert>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "alert-destructive",
"type": "registry:example",
"registryDependencies": [
"alert"
],
"files": [
{
"path": "example/alert-destructive.tsx",
"content": "import { AlertCircle } from \"lucide-react\"\n\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n} from \"@/registry/default/ui/alert\"\n\nexport default function AlertDestructive() {\n return (\n <Alert variant=\"destructive\">\n <AlertCircle className=\"h-4 w-4\" />\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>\n Your session has expired. Please log in again.\n </AlertDescription>\n </Alert>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,16 @@
{
"name": "alert-dialog-demo",
"type": "registry:example",
"registryDependencies": [
"alert-dialog",
"button"
],
"files": [
{
"path": "example/alert-dialog-demo.tsx",
"content": "import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/registry/default/ui/alert-dialog\"\nimport { Button } from \"@/registry/default/ui/button\"\n\nexport default function AlertDialogDemo() {\n return (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\">Show Dialog</Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>\n <AlertDialogDescription>\n This action cannot be undone. This will permanently delete your\n account and remove your data from our servers.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "aspect-ratio-demo",
"type": "registry:example",
"registryDependencies": [
"aspect-ratio"
],
"files": [
{
"path": "example/aspect-ratio-demo.tsx",
"content": "import Image from \"next/image\"\n\nimport { AspectRatio } from \"@/registry/default/ui/aspect-ratio\"\n\nexport default function AspectRatioDemo() {\n return (\n <AspectRatio ratio={16 / 9} className=\"bg-muted\">\n <Image\n src=\"https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80\"\n alt=\"Photo by Drew Beamer\"\n fill\n className=\"h-full w-full rounded-md object-cover\"\n />\n </AspectRatio>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "avatar-demo",
"type": "registry:example",
"registryDependencies": [
"avatar"
],
"files": [
{
"path": "example/avatar-demo.tsx",
"content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\"\n\nexport default function AvatarDemo() {\n return (\n <Avatar>\n <AvatarImage src=\"https://github.com/shadcn.png\" alt=\"@shadcn\" />\n <AvatarFallback>CN</AvatarFallback>\n </Avatar>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "badge-demo",
"type": "registry:example",
"registryDependencies": [
"badge"
],
"files": [
{
"path": "example/badge-demo.tsx",
"content": "import { Badge } from \"@/registry/default/ui/badge\"\n\nexport default function BadgeDemo() {\n return <Badge>Badge</Badge>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "badge-destructive",
"type": "registry:example",
"registryDependencies": [
"badge"
],
"files": [
{
"path": "example/badge-destructive.tsx",
"content": "import { Badge } from \"@/registry/default/ui/badge\"\n\nexport default function BadgeDestructive() {\n return <Badge variant=\"destructive\">Destructive</Badge>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "badge-outline",
"type": "registry:example",
"registryDependencies": [
"badge"
],
"files": [
{
"path": "example/badge-outline.tsx",
"content": "import { Badge } from \"@/registry/default/ui/badge\"\n\nexport default function BadgeOutline() {\n return <Badge variant=\"outline\">Outline</Badge>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "badge-secondary",
"type": "registry:example",
"registryDependencies": [
"badge"
],
"files": [
{
"path": "example/badge-secondary.tsx",
"content": "import { Badge } from \"@/registry/default/ui/badge\"\n\nexport default function BadgeSecondary() {\n return <Badge variant=\"secondary\">Secondary</Badge>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-demo",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-demo.tsx",
"content": "import {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\n\nexport default function BreadcrumbDemo() {\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">Home</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger className=\"flex items-center gap-1\">\n <BreadcrumbEllipsis className=\"h-4 w-4\" />\n <span className=\"sr-only\">Toggle menu</span>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem>Documentation</DropdownMenuItem>\n <DropdownMenuItem>Themes</DropdownMenuItem>\n <DropdownMenuItem>GitHub</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/docs/components\">Components</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-dropdown",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-dropdown.tsx",
"content": "import { ChevronDown, Slash } from \"lucide-react\"\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\n\nexport default function BreadcrumbWithDropdown() {\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">Home</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger className=\"flex items-center gap-1\">\n Components\n <ChevronDown className=\"h-4 w-4\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem>Documentation</DropdownMenuItem>\n <DropdownMenuItem>Themes</DropdownMenuItem>\n <DropdownMenuItem>GitHub</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-ellipsis",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-ellipsis.tsx",
"content": "import Link from \"next/link\"\n\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\n\nexport default function BreadcrumbCollapsed() {\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink asChild>\n <Link href=\"/\">Home</Link>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbEllipsis />\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink asChild>\n <Link href=\"/docs/components\">Components</Link>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-link",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-link.tsx",
"content": "import Link from \"next/link\"\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\n\nexport default function BreadcrumbWithCustomSeparator() {\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink>\n <Link href=\"/\">Home</Link>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink>\n <Link href=\"/components\">Components</Link>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-responsive",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-responsive.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport Link from \"next/link\"\n\nimport { useMediaQuery } from \"@/hooks/use-media-query\"\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\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 Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n DrawerTrigger,\n} from \"@/registry/default/ui/drawer\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/registry/default/ui/dropdown-menu\"\n\nconst items = [\n { href: \"#\", label: \"Home\" },\n { href: \"#\", label: \"Documentation\" },\n { href: \"#\", label: \"Building Your Application\" },\n { href: \"#\", label: \"Data Fetching\" },\n { label: \"Caching and Revalidating\" },\n]\n\nconst ITEMS_TO_DISPLAY = 3\n\nexport default function BreadcrumbResponsive() {\n const [open, setOpen] = React.useState(false)\n const isDesktop = useMediaQuery(\"(min-width: 768px)\")\n\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href={items[0].href}>{items[0].label}</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n {items.length > ITEMS_TO_DISPLAY ? (\n <>\n <BreadcrumbItem>\n {isDesktop ? (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger\n className=\"flex items-center gap-1\"\n aria-label=\"Toggle menu\"\n >\n <BreadcrumbEllipsis className=\"h-4 w-4\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {items.slice(1, -2).map((item, index) => (\n <DropdownMenuItem key={index}>\n <Link href={item.href ? item.href : \"#\"}>\n {item.label}\n </Link>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <Drawer open={open} onOpenChange={setOpen}>\n <DrawerTrigger aria-label=\"Toggle Menu\">\n <BreadcrumbEllipsis className=\"h-4 w-4\" />\n </DrawerTrigger>\n <DrawerContent>\n <DrawerHeader className=\"text-left\">\n <DrawerTitle>Navigate to</DrawerTitle>\n <DrawerDescription>\n Select a page to navigate to.\n </DrawerDescription>\n </DrawerHeader>\n <div className=\"grid gap-1 px-4\">\n {items.slice(1, -2).map((item, index) => (\n <Link\n key={index}\n href={item.href ? item.href : \"#\"}\n className=\"py-1 text-sm\"\n >\n {item.label}\n </Link>\n ))}\n </div>\n <DrawerFooter className=\"pt-4\">\n <DrawerClose asChild>\n <Button variant=\"outline\">Close</Button>\n </DrawerClose>\n </DrawerFooter>\n </DrawerContent>\n </Drawer>\n )}\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n </>\n ) : null}\n {items.slice(-ITEMS_TO_DISPLAY + 1).map((item, index) => (\n <BreadcrumbItem key={index}>\n {item.href ? (\n <>\n <BreadcrumbLink\n asChild\n className=\"max-w-20 truncate md:max-w-none\"\n >\n <Link href={item.href}>{item.label}</Link>\n </BreadcrumbLink>\n <BreadcrumbSeparator />\n </>\n ) : (\n <BreadcrumbPage className=\"max-w-20 truncate md:max-w-none\">\n {item.label}\n </BreadcrumbPage>\n )}\n </BreadcrumbItem>\n ))}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "breadcrumb-separator",
"type": "registry:example",
"registryDependencies": [
"breadcrumb"
],
"files": [
{
"path": "example/breadcrumb-separator.tsx",
"content": "import { Slash } from \"lucide-react\"\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\n\nexport default function BreadcrumbWithCustomSeparator() {\n return (\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">Home</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/components\">Components</BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-as-child",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-as-child.tsx",
"content": "import Link from \"next/link\"\n\nimport { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonAsChild() {\n return (\n <Button asChild>\n <Link href=\"/login\">Login</Link>\n </Button>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-demo",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-demo.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonDemo() {\n return <Button>Button</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-destructive",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-destructive.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonDestructive() {\n return <Button variant=\"destructive\">Destructive</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-ghost",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-ghost.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonGhost() {\n return <Button variant=\"ghost\">Ghost</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-icon",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-icon.tsx",
"content": "import { ChevronRight } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonIcon() {\n return (\n <Button variant=\"outline\" size=\"icon\">\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-link",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-link.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonLink() {\n return <Button variant=\"link\">Link</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-loading",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-loading.tsx",
"content": "import { Loader2 } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonLoading() {\n return (\n <Button disabled>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n Please wait\n </Button>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-outline",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-outline.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonOutline() {\n return <Button variant=\"outline\">Outline</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-secondary",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-secondary.tsx",
"content": "import { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonSecondary() {\n return <Button variant=\"secondary\">Secondary</Button>\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "button-with-icon",
"type": "registry:example",
"registryDependencies": [
"button"
],
"files": [
{
"path": "example/button-with-icon.tsx",
"content": "import { Mail } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\n\nexport default function ButtonWithIcon() {\n return (\n <Button>\n <Mail /> Login with Email\n </Button>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "calendar-demo",
"type": "registry:example",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "example/calendar-demo.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function CalendarDemo() {\n const [date, setDate] = React.useState<Date | undefined>(new Date())\n\n return (\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n className=\"rounded-md border\"\n />\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,17 @@
{
"name": "calendar-form",
"type": "registry:example",
"registryDependencies": [
"calendar",
"form",
"popover"
],
"files": [
{
"path": "example/calendar-form.tsx",
"content": "\"use client\"\n\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { format } from \"date-fns\"\nimport { CalendarIcon } from \"lucide-react\"\nimport { useForm } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { cn } from \"@/lib/utils\"\nimport { toast } from \"@/registry/default/hooks/use-toast\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from \"@/registry/default/ui/form\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nconst FormSchema = z.object({\n dob: z.date({\n required_error: \"A date of birth is required.\",\n }),\n})\n\nexport default function CalendarForm() {\n const form = useForm<z.infer<typeof FormSchema>>({\n resolver: zodResolver(FormSchema),\n })\n\n function onSubmit(data: z.infer<typeof FormSchema>) {\n toast({\n title: \"You submitted the following values:\",\n description: (\n <pre className=\"mt-2 w-[340px] rounded-md bg-slate-950 p-4\">\n <code className=\"text-white\">{JSON.stringify(data, null, 2)}</code>\n </pre>\n ),\n })\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-8\">\n <FormField\n control={form.control}\n name=\"dob\"\n render={({ field }) => (\n <FormItem className=\"flex flex-col\">\n <FormLabel>Date of birth</FormLabel>\n <Popover>\n <PopoverTrigger asChild>\n <FormControl>\n <Button\n variant={\"outline\"}\n className={cn(\n \"w-[240px] pl-3 text-left font-normal\",\n !field.value && \"text-muted-foreground\"\n )}\n >\n {field.value ? (\n format(field.value, \"PPP\")\n ) : (\n <span>Pick a date</span>\n )}\n <CalendarIcon className=\"ml-auto h-4 w-4 opacity-50\" />\n </Button>\n </FormControl>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n <Calendar\n mode=\"single\"\n selected={field.value}\n onSelect={field.onChange}\n disabled={(date) =>\n date > new Date() || date < new Date(\"1900-01-01\")\n }\n initialFocus\n />\n </PopoverContent>\n </Popover>\n <FormDescription>\n Your date of birth is used to calculate your age.\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\">Submit</Button>\n </form>\n </Form>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,17 @@
{
"name": "card-demo",
"type": "registry:example",
"registryDependencies": [
"card",
"button",
"switch"
],
"files": [
{
"path": "example/card-demo.tsx",
"content": "import { BellRing, Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Switch } from \"@/registry/default/ui/switch\"\n\nconst notifications = [\n {\n title: \"Your call has been confirmed.\",\n description: \"1 hour ago\",\n },\n {\n title: \"You have a new message!\",\n description: \"1 hour ago\",\n },\n {\n title: \"Your subscription is expiring soon!\",\n description: \"2 hours ago\",\n },\n]\n\ntype CardProps = React.ComponentProps<typeof Card>\n\nexport default function CardDemo({ className, ...props }: CardProps) {\n return (\n <Card className={cn(\"w-[380px]\", className)} {...props}>\n <CardHeader>\n <CardTitle>Notifications</CardTitle>\n <CardDescription>You have 3 unread messages.</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4\">\n <div className=\" flex items-center space-x-4 rounded-md border p-4\">\n <BellRing />\n <div className=\"flex-1 space-y-1\">\n <p className=\"text-sm font-medium leading-none\">\n Push Notifications\n </p>\n <p className=\"text-sm text-muted-foreground\">\n Send notifications to device.\n </p>\n </div>\n <Switch />\n </div>\n <div>\n {notifications.map((notification, index) => (\n <div\n key={index}\n className=\"mb-4 grid grid-cols-[25px_1fr] items-start pb-4 last:mb-0 last:pb-0\"\n >\n <span className=\"flex h-2 w-2 translate-y-1 rounded-full bg-sky-500\" />\n <div className=\"space-y-1\">\n <p className=\"text-sm font-medium leading-none\">\n {notification.title}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {notification.description}\n </p>\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n <CardFooter>\n <Button className=\"w-full\">\n <Check /> Mark all as read\n </Button>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,19 @@
{
"name": "card-with-form",
"type": "registry:example",
"registryDependencies": [
"button",
"card",
"input",
"label",
"select"
],
"files": [
{
"path": "example/card-with-form.tsx",
"content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\"\n\nexport default function CardWithForm() {\n return (\n <Card className=\"w-[350px]\">\n <CardHeader>\n <CardTitle>Create project</CardTitle>\n <CardDescription>Deploy your new project in one-click.</CardDescription>\n </CardHeader>\n <CardContent>\n <form>\n <div className=\"grid w-full items-center gap-4\">\n <div className=\"flex flex-col space-y-1.5\">\n <Label htmlFor=\"name\">Name</Label>\n <Input id=\"name\" placeholder=\"Name of your project\" />\n </div>\n <div className=\"flex flex-col space-y-1.5\">\n <Label htmlFor=\"framework\">Framework</Label>\n <Select>\n <SelectTrigger id=\"framework\">\n <SelectValue placeholder=\"Select\" />\n </SelectTrigger>\n <SelectContent position=\"popper\">\n <SelectItem value=\"next\">Next.js</SelectItem>\n <SelectItem value=\"sveltekit\">SvelteKit</SelectItem>\n <SelectItem value=\"astro\">Astro</SelectItem>\n <SelectItem value=\"nuxt\">Nuxt.js</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n </form>\n </CardContent>\n <CardFooter className=\"flex justify-between\">\n <Button variant=\"outline\">Cancel</Button>\n <Button>Deploy</Button>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-api",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-api.tsx",
"content": "import * as React from \"react\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n type CarouselApi,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselDApiDemo() {\n const [api, setApi] = React.useState<CarouselApi>()\n const [current, setCurrent] = React.useState(0)\n const [count, setCount] = React.useState(0)\n\n React.useEffect(() => {\n if (!api) {\n return\n }\n\n setCount(api.scrollSnapList().length)\n setCurrent(api.selectedScrollSnap() + 1)\n\n api.on(\"select\", () => {\n setCurrent(api.selectedScrollSnap() + 1)\n })\n }, [api])\n\n return (\n <div className=\"mx-auto max-w-xs\">\n <Carousel setApi={setApi} className=\"w-full max-w-xs\">\n <CarouselContent>\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index}>\n <Card>\n <CardContent className=\"flex aspect-square items-center justify-center p-6\">\n <span className=\"text-4xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n <div className=\"py-2 text-center text-sm text-muted-foreground\">\n Slide {current} of {count}\n </div>\n </div>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-demo",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-demo.tsx",
"content": "import * as React from \"react\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselDemo() {\n return (\n <Carousel className=\"w-full max-w-xs\">\n <CarouselContent>\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index}>\n <div className=\"p-1\">\n <Card>\n <CardContent className=\"flex aspect-square items-center justify-center p-6\">\n <span className=\"text-4xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-orientation",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-orientation.tsx",
"content": "import * as React from \"react\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselOrientation() {\n return (\n <Carousel\n opts={{\n align: \"start\",\n }}\n orientation=\"vertical\"\n className=\"w-full max-w-xs\"\n >\n <CarouselContent className=\"-mt-1 h-[200px]\">\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index} className=\"pt-1 md:basis-1/2\">\n <div className=\"p-1\">\n <Card>\n <CardContent className=\"flex items-center justify-center p-6\">\n <span className=\"text-3xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-plugin",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-plugin.tsx",
"content": "import * as React from \"react\"\nimport Autoplay from \"embla-carousel-autoplay\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselPlugin() {\n const plugin = React.useRef(\n Autoplay({ delay: 2000, stopOnInteraction: true })\n )\n\n return (\n <Carousel\n plugins={[plugin.current]}\n className=\"w-full max-w-xs\"\n onMouseEnter={plugin.current.stop}\n onMouseLeave={plugin.current.reset}\n >\n <CarouselContent>\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index}>\n <div className=\"p-1\">\n <Card>\n <CardContent className=\"flex aspect-square items-center justify-center p-6\">\n <span className=\"text-4xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-size",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-size.tsx",
"content": "import * as React from \"react\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselSize() {\n return (\n <Carousel\n opts={{\n align: \"start\",\n }}\n className=\"w-full max-w-sm\"\n >\n <CarouselContent>\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index} className=\"md:basis-1/2 lg:basis-1/3\">\n <div className=\"p-1\">\n <Card>\n <CardContent className=\"flex aspect-square items-center justify-center p-6\">\n <span className=\"text-3xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,15 @@
{
"name": "carousel-spacing",
"type": "registry:example",
"registryDependencies": [
"carousel"
],
"files": [
{
"path": "example/carousel-spacing.tsx",
"content": "import * as React from \"react\"\n\nimport { Card, CardContent } from \"@/registry/default/ui/card\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/registry/default/ui/carousel\"\n\nexport default function CarouselSpacing() {\n return (\n <Carousel className=\"w-full max-w-sm\">\n <CarouselContent className=\"-ml-1\">\n {Array.from({ length: 5 }).map((_, index) => (\n <CarouselItem key={index} className=\"pl-1 md:basis-1/2 lg:basis-1/3\">\n <div className=\"p-1\">\n <Card>\n <CardContent className=\"flex aspect-square items-center justify-center p-6\">\n <span className=\"text-2xl font-semibold\">{index + 1}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-axes",
"type": "registry:block",
"description": "An area chart with axes",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-default",
"type": "registry:block",
"description": "A simple area chart",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-gradient",
"type": "registry:block",
"description": "An area chart with gradient fill",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-icons",
"type": "registry:block",
"description": "An area chart with icons",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-interactive",
"type": "registry:block",
"description": "An interactive area chart",
"registryDependencies": [
"card",
"chart",

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-legend",
"type": "registry:block",
"description": "An area chart with a legend",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-linear",
"type": "registry:block",
"description": "A linear area chart",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-stacked-expand",
"type": "registry:block",
"description": "A stacked area chart with expand stacking",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-stacked",
"type": "registry:block",
"description": "A stacked area chart",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-area-step",
"type": "registry:block",
"description": "A step area chart",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-bar-active",
"type": "registry:block",
"description": "A bar chart with an active bar",
"registryDependencies": [
"card",
"chart"

View File

@@ -1,6 +1,7 @@
{
"name": "chart-bar-default",
"type": "registry:block",
"description": "A bar chart",
"registryDependencies": [
"card",
"chart"

View File

@@ -0,0 +1,12 @@
{
"name": "chart-bar-demo-axis",
"type": "registry:example",
"files": [
{
"path": "example/chart-bar-demo-axis.tsx",
"content": "\"use client\"\n\nimport { Bar, BarChart, CartesianGrid, XAxis } from \"recharts\"\n\nimport { ChartConfig, ChartContainer } from \"@/registry/default/ui/chart\"\n\nconst chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"#2563eb\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"#60a5fa\",\n },\n} satisfies ChartConfig\n\nexport default function Component() {\n return (\n <ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <CartesianGrid vertical={false} />\n <XAxis\n dataKey=\"month\"\n tickLine={false}\n tickMargin={10}\n axisLine={false}\n tickFormatter={(value) => value.slice(0, 3)}\n />\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n </ChartContainer>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "chart-bar-demo-grid",
"type": "registry:example",
"files": [
{
"path": "example/chart-bar-demo-grid.tsx",
"content": "\"use client\"\n\nimport { Bar, BarChart, CartesianGrid } from \"recharts\"\n\nimport { ChartConfig, ChartContainer } from \"@/registry/default/ui/chart\"\n\nconst chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"#2563eb\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"#60a5fa\",\n },\n} satisfies ChartConfig\n\nexport default function Component() {\n return (\n <ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <CartesianGrid vertical={false} />\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n </ChartContainer>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "chart-bar-demo-legend",
"type": "registry:example",
"files": [
{
"path": "example/chart-bar-demo-legend.tsx",
"content": "\"use client\"\n\nimport { Bar, BarChart, CartesianGrid, XAxis } from \"recharts\"\n\nimport {\n ChartConfig,\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n} from \"@/registry/default/ui/chart\"\n\nconst chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"#2563eb\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"#60a5fa\",\n },\n} satisfies ChartConfig\n\nexport default function Component() {\n return (\n <ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <CartesianGrid vertical={false} />\n <XAxis\n dataKey=\"month\"\n tickLine={false}\n tickMargin={10}\n axisLine={false}\n tickFormatter={(value) => value.slice(0, 3)}\n />\n <ChartTooltip content={<ChartTooltipContent />} />\n <ChartLegend content={<ChartLegendContent />} />\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n </ChartContainer>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "chart-bar-demo-tooltip",
"type": "registry:example",
"files": [
{
"path": "example/chart-bar-demo-tooltip.tsx",
"content": "\"use client\"\n\nimport { Bar, BarChart, CartesianGrid, XAxis } from \"recharts\"\n\nimport {\n ChartConfig,\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n} from \"@/registry/default/ui/chart\"\n\nconst chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"#2563eb\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"#60a5fa\",\n },\n} satisfies ChartConfig\n\nexport default function Component() {\n return (\n <ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <CartesianGrid vertical={false} />\n <XAxis\n dataKey=\"month\"\n tickLine={false}\n tickMargin={10}\n axisLine={false}\n tickFormatter={(value) => value.slice(0, 3)}\n />\n <ChartTooltip content={<ChartTooltipContent />} />\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n </ChartContainer>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "chart-bar-demo",
"type": "registry:example",
"files": [
{
"path": "example/chart-bar-demo.tsx",
"content": "\"use client\"\n\nimport { Bar, BarChart } from \"recharts\"\n\nimport { ChartConfig, ChartContainer } from \"@/registry/default/ui/chart\"\n\nconst chartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst chartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"#2563eb\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"#60a5fa\",\n },\n} satisfies ChartConfig\n\nexport default function Component() {\n return (\n <ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n </ChartContainer>\n )\n}\n",
"type": "registry:example",
"target": ""
}
]
}

View File

@@ -1,6 +1,7 @@
{
"name": "chart-bar-horizontal",
"type": "registry:block",
"description": "A horizontal bar chart",
"registryDependencies": [
"card",
"chart"

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