feat: blocks (#3094)

This commit is contained in:
shadcn
2024-03-22 21:39:33 +04:00
committed by GitHub
parent 3a4c3b2f7d
commit 79c054ac7a
160 changed files with 7499 additions and 2407 deletions

View File

@@ -0,0 +1,27 @@
name: "Request a block"
description: "Request a new block for shadcn/ui."
title: "[blocks]: "
labels: ["area: blocks", "area: request"]
body:
- type: markdown
attributes:
value: |
### Thanks for taking the time to create a block request! Please search open/closed requests before submitting, as the block or a similar one may have already been requested.
- type: textarea
id: block-description
attributes:
label: Description
description: Tell us about your block request
placeholder: "A dashboard for an e-commerce website showing sales, orders, and customers..."
validations:
required: true
- type: input
id: block-example-url
attributes:
label: Example
description: Link to an example of the block
placeholder: ex. https://example.com
validations:
required: false

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
"use server"
import { track } from "@vercel/analytics/server"
const EDIT_IN_V0_SOURCE = "ui.shadcn.com"
export async function editInV0({
name,
description,
style,
code,
}: {
name: string
description: string
style: string
code: string
}) {
try {
await track("edit_in_v0", {
name,
description,
style,
})
const response = await fetch(`${process.env.V0_URL}/api/edit`, {
method: "POST",
body: JSON.stringify({ description, code, source: EDIT_IN_V0_SOURCE }),
headers: {
"x-v0-edit-secret": process.env.V0_EDIT_SECRET!,
"x-vercel-protection-bypass":
process.env.DEPLOYMENT_PROTECTION_BYPASS || "not-set",
"Content-Type": "application/json",
},
})
if (!response.ok) {
if (response.status === 403) {
throw new Error("Unauthorized")
}
throw new Error("Something went wrong. Please try again later.")
}
return await response.json()
} catch (error) {
if (error instanceof Error) {
return { error: error.message }
}
}
}

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 { Button } from "@/registry/new-york/ui/button"
export const metadata: Metadata = {
title: "Building Blocks.",
description:
"Beautifully designed. Copy and paste into your apps. Open Source.",
}
export default function BlocksLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="container relative">
<PageHeader className="max-w-3xl">
<Announcement />
<PageHeaderHeading className="text-balance">
Building Blocks for the Web
</PageHeaderHeading>
<PageHeaderDescription>
Beautifully designed. Copy and paste into your apps. Open Source.
</PageHeaderDescription>
<PageActions>
<Button asChild>
<a href="#blocks">Browse</a>
</Button>
<Button asChild variant="outline">
<a
href="https://github.com/shadcn-ui/ui/issues/new/choose"
target="_blank"
>
Request a block
</a>
</Button>
</PageActions>
</PageHeader>
<section id="blocks" className="grid scroll-mt-24 gap-24 lg:gap-48">
{children}
</section>
</div>
)
}

View File

@@ -0,0 +1,10 @@
import { getAllBlockIds } from "@/lib/blocks"
import { BlockDisplay } from "@/components/block-display"
export default async function BlocksPage() {
const blocks = await getAllBlockIds()
return blocks.map((name, index) => (
<BlockDisplay key={`${name}-${index}`} name={name} />
))
}

View File

@@ -4,7 +4,7 @@ import Link from "next/link"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/registry/new-york/ui/button"
import { UserAuthForm } from "@/app/examples/authentication/components/user-auth-form"
import { UserAuthForm } from "@/app/(app)/examples/authentication/components/user-auth-form"
export const metadata: Metadata = {
title: "Authentication",

View File

@@ -15,13 +15,13 @@ import {
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { CalendarDateRangePicker } from "@/app/examples/dashboard/components/date-range-picker"
import { MainNav } from "@/app/examples/dashboard/components/main-nav"
import { Overview } from "@/app/examples/dashboard/components/overview"
import { RecentSales } from "@/app/examples/dashboard/components/recent-sales"
import { Search } from "@/app/examples/dashboard/components/search"
import TeamSwitcher from "@/app/examples/dashboard/components/team-switcher"
import { UserNav } from "@/app/examples/dashboard/components/user-nav"
import { CalendarDateRangePicker } from "@/app/(app)/examples/dashboard/components/date-range-picker"
import { MainNav } from "@/app/(app)/examples/dashboard/components/main-nav"
import { Overview } from "@/app/(app)/examples/dashboard/components/overview"
import { RecentSales } from "@/app/(app)/examples/dashboard/components/recent-sales"
import { Search } from "@/app/(app)/examples/dashboard/components/search"
import TeamSwitcher from "@/app/(app)/examples/dashboard/components/team-switcher"
import { UserNav } from "@/app/(app)/examples/dashboard/components/user-nav"
export const metadata: Metadata = {
title: "Dashboard",

View File

@@ -1,5 +1,5 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { AccountForm } from "@/app/examples/forms/account/account-form"
import { AccountForm } from "@/app/(app)/examples/forms/account/account-form"
export default function SettingsAccountPage() {
return (

View File

@@ -1,5 +1,5 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { AppearanceForm } from "@/app/examples/forms/appearance/appearance-form"
import { AppearanceForm } from "@/app/(app)/examples/forms/appearance/appearance-form"
export default function SettingsAppearancePage() {
return (

View File

@@ -1,5 +1,5 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { DisplayForm } from "@/app/examples/forms/display/display-form"
import { DisplayForm } from "@/app/(app)/examples/forms/display/display-form"
export default function SettingsDisplayPage() {
return (

View File

@@ -2,7 +2,7 @@ import { Metadata } from "next"
import Image from "next/image"
import { Separator } from "@/registry/new-york/ui/separator"
import { SidebarNav } from "@/app/examples/forms/components/sidebar-nav"
import { SidebarNav } from "@/app/(app)/examples/forms/components/sidebar-nav"
export const metadata: Metadata = {
title: "Forms",

View File

@@ -1,5 +1,5 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { NotificationsForm } from "@/app/examples/forms/notifications/notifications-form"
import { NotificationsForm } from "@/app/(app)/examples/forms/notifications/notifications-form"
export default function SettingsNotificationsPage() {
return (

View File

@@ -1,5 +1,5 @@
import { Separator } from "@/registry/new-york/ui/separator"
import { ProfileForm } from "@/app/examples/forms/profile-form"
import { ProfileForm } from "@/app/(app)/examples/forms/profile-form"
export default function SettingsProfilePage() {
return (

View File

@@ -42,7 +42,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
import { Mail } from "@/app/examples/mail/data"
import { Mail } from "@/app/(app)/examples/mail/data"
interface MailDisplayProps {
mail: Mail | null

View File

@@ -5,8 +5,8 @@ import { cn } from "@/lib/utils"
import { Badge } from "@/registry/new-york/ui/badge"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
import { Separator } from "@/registry/new-york/ui/separator"
import { Mail } from "@/app/examples/mail/data"
import { useMail } from "@/app/examples/mail/use-mail"
import { Mail } from "@/app/(app)/examples/mail/data"
import { useMail } from "@/app/(app)/examples/mail/use-mail"
interface MailListProps {
items: Mail[]

View File

@@ -1,4 +1,5 @@
"use client"
import * as React from "react"
import {
AlertCircle,
@@ -7,7 +8,6 @@ import {
File,
Inbox,
MessagesSquare,
PenBox,
Search,
Send,
ShoppingCart,
@@ -15,15 +15,14 @@ import {
Users2,
} from "lucide-react"
import { AccountSwitcher } from "@/app/examples/mail/components/account-switcher"
import { MailDisplay } from "@/app/examples/mail/components/mail-display"
import { MailList } from "@/app/examples/mail/components/mail-list"
import { Nav } from "@/app/examples/mail/components/nav"
import { Mail } from "@/app/examples/mail/data"
import { useMail } from "@/app/examples/mail/use-mail"
import { cn } from "@/lib/utils"
import { Separator } from "@/registry/new-york/ui/separator"
import { Input } from "@/registry/new-york/ui/input"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/new-york/ui/resizable"
import { Separator } from "@/registry/new-york/ui/separator"
import {
Tabs,
TabsContent,
@@ -31,7 +30,12 @@ import {
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import { TooltipProvider } from "@/registry/new-york/ui/tooltip"
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/registry/new-york/ui/resizable"
import { AccountSwitcher } from "@/app/(app)/examples/mail/components/account-switcher"
import { MailDisplay } from "@/app/(app)/examples/mail/components/mail-display"
import { MailList } from "@/app/(app)/examples/mail/components/mail-list"
import { Nav } from "@/app/(app)/examples/mail/components/nav"
import { type Mail } from "@/app/(app)/examples/mail/data"
import { useMail } from "@/app/(app)/examples/mail/use-mail"
interface MailProps {
accounts: {
@@ -78,9 +82,17 @@ export function Mail({
collapsed
)}`
}}
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
className={cn(
isCollapsed &&
"min-w-[50px] transition-all duration-300 ease-in-out"
)}
>
<div className={cn("flex h-[52px] items-center justify-center", isCollapsed ? 'h-[52px]': 'px-2')}>
<div
className={cn(
"flex h-[52px] items-center justify-center",
isCollapsed ? "h-[52px]" : "px-2"
)}
>
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
</div>
<Separator />
@@ -168,8 +180,18 @@ export function Mail({
<div className="flex items-center px-4 py-2">
<h1 className="text-xl font-bold">Inbox</h1>
<TabsList className="ml-auto">
<TabsTrigger value="all" className="text-zinc-600 dark:text-zinc-200">All mail</TabsTrigger>
<TabsTrigger value="unread" className="text-zinc-600 dark:text-zinc-200">Unread</TabsTrigger>
<TabsTrigger
value="all"
className="text-zinc-600 dark:text-zinc-200"
>
All mail
</TabsTrigger>
<TabsTrigger
value="unread"
className="text-zinc-600 dark:text-zinc-200"
>
Unread
</TabsTrigger>
</TabsList>
</div>
<Separator />

View File

@@ -1,8 +1,8 @@
import { cookies } from "next/headers"
import Image from "next/image"
import { Mail } from "@/app/examples/mail/components/mail"
import { accounts, mails } from "@/app/examples/mail/data"
import { Mail } from "@/app/(app)/examples/mail/components/mail"
import { accounts, mails } from "@/app/(app)/examples/mail/data"
export default function MailPage() {
const layout = cookies().get("react-resizable-panels:layout")

View File

@@ -1,6 +1,6 @@
import { atom, useAtom } from "jotai"
import { Mail, mails } from "@/app/examples/mail/data"
import { Mail, mails } from "@/app/(app)/examples/mail/data"
type Config = {
selected: Mail["id"] | null

View File

@@ -5,7 +5,7 @@ import { Table } from "@tanstack/react-table"
import { Button } from "@/registry/new-york/ui/button"
import { Input } from "@/registry/new-york/ui/input"
import { DataTableViewOptions } from "@/app/examples/tasks/components/data-table-view-options"
import { DataTableViewOptions } from "@/app/(app)/examples/tasks/components/data-table-view-options"
import { priorities, statuses } from "../data/data"
import { DataTableFacetedFilter } from "./data-table-faceted-filter"

View File

@@ -25,8 +25,8 @@ import {
TableRow,
} from "@/registry/new-york/ui/table"
import { DataTablePagination } from "../components/data-table-pagination"
import { DataTableToolbar } from "../components/data-table-toolbar"
import { DataTablePagination } from "./data-table-pagination"
import { DataTableToolbar } from "./data-table-toolbar"
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]

View File

@@ -17,7 +17,7 @@ export const metadata: Metadata = {
// Simulate a database read for tasks.
async function getTasks() {
const data = await fs.readFile(
path.join(process.cwd(), "app/examples/tasks/data/tasks.json")
path.join(process.cwd(), "app/(app)/examples/tasks/data/tasks.json")
)
const tasks = JSON.parse(data.toString())

View File

@@ -0,0 +1,16 @@
import { SiteFooter } from "@/components/site-footer"
import { SiteHeader } from "@/components/site-header"
interface AppLayoutProps {
children: React.ReactNode
}
export default function AppLayout({ children }: AppLayoutProps) {
return (
<>
<SiteHeader />
<main className="flex-1">{children}</main>
<SiteFooter />
</>
)
}

View File

@@ -13,7 +13,7 @@ import {
PageHeaderHeading,
} from "@/components/page-header"
import { buttonVariants } from "@/registry/new-york/ui/button"
import MailPage from "@/app/examples/mail/page"
import MailPage from "@/app/(app)/examples/mail/page"
export default function IndexPage() {
return (

View File

@@ -10,7 +10,7 @@ import {
} from "@/components/page-header"
import { ThemeCustomizer } from "@/components/theme-customizer"
import { ThemeWrapper } from "@/components/theme-wrapper"
import { ThemesTabs } from "@/app/themes/tabs"
import { ThemesTabs } from "@/app/(app)/themes/tabs"
export const metadata: Metadata = {
title: "Themes",

View File

@@ -0,0 +1,86 @@
import { siteConfig } from "@/config/site"
import { getAllBlockIds, getBlock } from "@/lib/blocks"
import { absoluteUrl } from "@/lib/utils"
import { Style, styles } from "@/registry/styles"
import "@/styles/mdx.css"
import { Metadata } from "next"
import { notFound } from "next/navigation"
export async function generateMetadata({
params,
}: {
params: {
style: Style["name"]
name: string
}
}): Promise<Metadata> {
const { name, style } = params
const block = await getBlock(name, style)
if (!block) {
return {}
}
return {
title: block.name,
description: block.description,
openGraph: {
title: block.name,
description: block.description,
type: "article",
url: absoluteUrl(`/blocks/${block.name}`),
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
},
],
},
twitter: {
card: "summary_large_image",
title: block.name,
description: block.description,
images: [siteConfig.ogImage],
creator: "@shadcn",
},
}
}
export async function generateStaticParams() {
const blockIds = await getAllBlockIds()
return styles
.map((style) =>
blockIds.map((name) => ({
style: style.name,
name,
}))
)
.flat()
}
export default async function BlockPage({
params,
}: {
params: {
style: Style["name"]
name: string
}
}) {
const { name, style } = params
const block = await getBlock(name, style)
if (!block) {
return notFound()
}
const Component = block.component
return (
<div className={block.container?.className || ""}>
<Component />
</div>
)
}

View File

@@ -6,8 +6,6 @@ import { fontSans } from "@/lib/fonts"
import { cn } from "@/lib/utils"
import { Analytics } from "@/components/analytics"
import { ThemeProvider } from "@/components/providers"
import { SiteFooter } from "@/components/site-footer"
import { SiteHeader } from "@/components/site-header"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeSwitcher } from "@/components/theme-switcher"
import { Toaster as DefaultToaster } from "@/registry/default/ui/toaster"
@@ -85,7 +83,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.className
fontSans.variable
)}
>
<ThemeProvider
@@ -96,9 +94,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
>
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-screen flex-col bg-background">
<SiteHeader />
<main className="flex-1">{children}</main>
<SiteFooter />
{children}
</div>
</div>
<TailwindIndicator />

View File

@@ -10,10 +10,8 @@ export function Announcement() {
className="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
>
🎉 <Separator className="mx-2 h-4" orientation="vertical" />{" "}
<span className="sm:hidden">New components and more.</span>
<span className="hidden sm:inline">
New components, breadcrumb and input otp.
</span>
<span className="sm:hidden">Introducing Blocks</span>
<span className="hidden sm:inline">Introducing Blocks</span>
<ArrowRightIcon className="ml-1 h-4 w-4" />
</Link>
)

View File

@@ -0,0 +1,54 @@
"use client"
import * as React from "react"
import { CheckIcon, ClipboardIcon } from "@radix-ui/react-icons"
import { trackEvent } from "@/lib/events"
import { Button } from "@/registry/new-york/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
export function BlockCopyCodeButton({
name,
code,
}: {
name: string
code: string
}) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
return (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="outline"
className="h-7 w-7 [&_svg]:size-3.5"
onClick={() => {
navigator.clipboard.writeText(code)
trackEvent({
name: "copy_block_code",
properties: {
name,
},
})
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
</TooltipTrigger>
<TooltipContent>Copy code</TooltipContent>
</Tooltip>
)
}

View File

@@ -0,0 +1,24 @@
import { getBlock } from "@/lib/blocks"
import { BlockPreview } from "@/components/block-preview"
import { styles } from "@/registry/styles"
export async function BlockDisplay({ name }: { name: string }) {
const blocks = await Promise.all(
styles.map(async (style) => {
const block = await getBlock(name, style.name)
// Cannot (and don't need to) pass component to the client.
delete block?.component
return block
})
)
if (!blocks?.length) {
return null
}
return blocks.map((block) => (
<BlockPreview key={`${block.style}-${block.name}`} block={block} />
))
}

View File

@@ -0,0 +1,210 @@
"use client"
import * as React from "react"
import {
CircleHelp,
Info,
Monitor,
Phone,
Smartphone,
Tablet,
} from "lucide-react"
import { ImperativePanelHandle } from "react-resizable-panels"
import { useConfig } from "@/hooks/use-config"
import { BlockCopyCodeButton } from "@/components/block-copy-code-button"
import { Icons } from "@/components/icons"
import { StyleSwitcher } from "@/components/style-switcher"
import { V0Button } from "@/components/v0-button"
import { Badge } from "@/registry/new-york/ui/badge"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/new-york/ui/resizable"
import { Separator } from "@/registry/new-york/ui/separator"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/new-york/ui/toggle-group"
import { Block } from "@/registry/schema"
export function BlockPreview({ block }: { block: Block }) {
const [config] = useConfig()
const [isLoading, setIsLoading] = React.useState(true)
const ref = React.useRef<ImperativePanelHandle>(null)
if (config.style !== block.style) {
return null
}
return (
<Tabs
id={block.name}
defaultValue="preview"
className="relative grid w-full scroll-m-20 gap-4"
style={
{
"--container-height": block.container?.height,
} as React.CSSProperties
}
>
<div className="flex flex-col items-center gap-4 sm:flex-row">
<div className="flex items-center gap-2">
<TabsList className="hidden sm:flex">
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
</TabsList>
<div className="hidden items-center gap-2 sm:flex">
<Separator
orientation="vertical"
className="mx-2 hidden h-4 md:flex"
/>
<div className="flex items-center gap-2">
<a href={`#${block.name}`}>
<Badge variant="outline">{block.name}</Badge>
</a>
<Popover>
<PopoverTrigger className="hidden text-muted-foreground hover:text-foreground sm:flex">
<Info className="h-3.5 w-3.5" />
<span className="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="right"
sideOffset={10}
className="text-sm"
>
{block.description}
</PopoverContent>
</Popover>
</div>
</div>
</div>
{block.code && (
<div className="flex items-center gap-2 pr-[14px] sm:ml-auto">
<div className="hidden h-[28px] items-center gap-1.5 rounded-md border p-[2px] shadow-sm md:flex">
<ToggleGroup
type="single"
defaultValue="100"
onValueChange={(value) => {
if (ref.current) {
ref.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="25"
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"
/>
<StyleSwitcher className="h-7" />
<Popover>
<PopoverTrigger className="hidden text-muted-foreground hover:text-foreground 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>
<Separator orientation="vertical" className="mx-2 h-4" />
<BlockCopyCodeButton name={block.name} code={block.code} />
<V0Button
name={block.name}
description={block.description || "Edit in v0"}
code={block.code}
style={block.style}
/>
</div>
)}
</div>
<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="relative rounded-lg border bg-background transition-all"
defaultSize={100}
minSize={25}
>
{isLoading ? (
<div className="absolute inset-0 z-10 flex h-[--container-height] w-full items-center justify-center gap-2 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}
className="relative z-20 w-full bg-background"
onLoad={() => {
setIsLoading(false)
}}
/>
</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>
</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>
)
}

View File

@@ -11,42 +11,42 @@ const examples = [
{
name: "Mail",
href: "/examples/mail",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/mail",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/mail",
},
{
name: "Dashboard",
href: "/examples/dashboard",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/dashboard",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/dashboard",
},
{
name: "Cards",
href: "/examples/cards",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/cards",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/cards",
},
{
name: "Tasks",
href: "/examples/tasks",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/tasks",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/tasks",
},
{
name: "Playground",
href: "/examples/playground",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/playground",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/playground",
},
{
name: "Forms",
href: "/examples/forms",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/forms",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/forms",
},
{
name: "Music",
href: "/examples/music",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/music",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/music",
},
{
name: "Authentication",
href: "/examples/authentication",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/authentication",
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/authentication",
},
]

View File

@@ -20,7 +20,7 @@ export function MainNav() {
{siteConfig.name}
</span>
</Link>
<nav className="flex items-center gap-6 text-sm">
<nav className="flex items-center gap-4 text-sm lg:gap-6">
<Link
href="/docs"
className={cn(
@@ -63,6 +63,17 @@ export function MainNav() {
>
Examples
</Link>
<Link
href="/blocks"
className={cn(
"transition-colors hover:text-foreground/80",
pathname?.startsWith("/blocks")
? "text-foreground"
: "text-foreground/60"
)}
>
Blocks
</Link>
<Link
href={siteConfig.links.github}
className={cn(

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
"use client"
import * as React from "react"

View File

@@ -9,7 +9,7 @@ import { TooltipProvider } from "@/registry/new-york/ui/tooltip"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider {...props}>
<TooltipProvider>{children}</TooltipProvider>
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
</NextThemesProvider>
)
}

View File

@@ -0,0 +1,117 @@
"use client"
import * as React from "react"
import { editInV0 } from "@/actions/edit-in-v0"
import { Loader2 } from "lucide-react"
import { useFormStatus } from "react-dom"
import { toast } from "sonner"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
import { Style } from "@/registry/styles"
export function V0Button({
name,
description,
code,
style,
}: {
name: string
description: string
code: string
style: Style["name"]
}) {
if (style === "new-york") {
return (
<Button
aria-label="Edit in v0"
className="h-7 gap-1"
size="sm"
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>
),
})
}
>
Edit in <V0Logo />
</Button>
)
}
return (
<form
action={async () => {
try {
const result = await editInV0({
name,
description,
code,
style,
})
if (result?.error) {
throw new Error(result.error)
}
if (result?.url) {
const popupOpened = window.open(result.url, "_blank")
if (!popupOpened) {
toast.warning("Pop-up window blocked.", {
description:
"Click the pop-up button in your browser to continue.",
duration: 5000,
})
}
}
} catch (error) {
if (error instanceof Error) {
toast.error(error.message)
}
}
}}
>
<Form />
</form>
)
}
function Form() {
const { pending } = useFormStatus()
return (
<Button
aria-label="Edit in v0"
className="h-7 gap-1"
size="sm"
disabled={pending}
>
{pending && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
Edit in <V0Logo />
</Button>
)
}
function V0Logo({ className, ...props }: React.ComponentProps<"svg">) {
return (
<svg
viewBox="0 0 40 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={cn("h-5 w-5 text-current", className)}
{...props}
>
<path
d="M23.3919 0H32.9188C36.7819 0 39.9136 3.13165 39.9136 6.99475V16.0805H36.0006V6.99475C36.0006 6.90167 35.9969 6.80925 35.9898 6.71766L26.4628 16.079C26.4949 16.08 26.5272 16.0805 26.5595 16.0805H36.0006V19.7762H26.5595C22.6964 19.7762 19.4788 16.6139 19.4788 12.7508V3.68923H23.3919V12.7508C23.3919 12.9253 23.4054 13.0977 23.4316 13.2668L33.1682 3.6995C33.0861 3.6927 33.003 3.68923 32.9188 3.68923H23.3919V0Z"
fill="currentColor"
></path>
<path
d="M13.7688 19.0956L0 3.68759H5.53933L13.6231 12.7337V3.68759H17.7535V17.5746C17.7535 19.6705 15.1654 20.6584 13.7688 19.0956Z"
fill="currentColor"
></path>
</svg>
)
}

View File

@@ -24,18 +24,8 @@ export const docsConfig: DocsConfig = {
href: "/examples",
},
{
title: "Figma",
href: "/docs/figma",
},
{
title: "GitHub",
href: "https://github.com/shadcn/ui",
external: true,
},
{
title: "Twitter",
href: "https://twitter.com/shadcn",
external: true,
title: "Blocks",
href: "/blocks",
},
],
sidebarNav: [

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