mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-25 21:56:08 +00:00
* feat(www): add login blocks * chore(www): restructure for blocks * chore: build registry * chore: clean up chunks * fix(www): chart categories * feat(www): big registry refactor * feat(www): update blocks * feat: complex blocks * fix: update schema * feat: sync new-york and default * fix: lint * feat: move charts * fix(www): code * fix: src path * chore: rebuild registry * fix: screenshot * fix: set new-york as default
177 lines
5.9 KiB
TypeScript
177 lines
5.9 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import Image from "next/image"
|
|
import { Index } from "@/__registry__"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { useConfig } from "@/hooks/use-config"
|
|
import { CopyButton } from "@/components/copy-button"
|
|
import { Icons } from "@/components/icons"
|
|
import { StyleSwitcher } from "@/components/style-switcher"
|
|
import { ThemeWrapper } from "@/components/theme-wrapper"
|
|
import { V0Button } from "@/components/v0-button"
|
|
import {
|
|
Tabs,
|
|
TabsContent,
|
|
TabsList,
|
|
TabsTrigger,
|
|
} from "@/registry/new-york/ui/tabs"
|
|
import { styles } from "@/registry/registry-styles"
|
|
|
|
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
name: string
|
|
extractClassname?: boolean
|
|
extractedClassNames?: string
|
|
align?: "center" | "start" | "end"
|
|
description?: string
|
|
hideCode?: boolean
|
|
type?: "block" | "component" | "example"
|
|
}
|
|
|
|
export function ComponentPreview({
|
|
name,
|
|
type,
|
|
children,
|
|
className,
|
|
extractClassname,
|
|
extractedClassNames,
|
|
align = "center",
|
|
description,
|
|
hideCode = false,
|
|
...props
|
|
}: ComponentPreviewProps) {
|
|
const [config] = useConfig()
|
|
const index = styles.findIndex((style) => style.name === config.style)
|
|
|
|
const Codes = React.Children.toArray(children) as React.ReactElement[]
|
|
const Code = Codes[index]
|
|
|
|
const Preview = React.useMemo(() => {
|
|
const Component = Index[config.style][name]?.component
|
|
|
|
if (!Component) {
|
|
return (
|
|
<p className="text-sm text-muted-foreground">
|
|
Component{" "}
|
|
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
|
{name}
|
|
</code>{" "}
|
|
not found in registry.
|
|
</p>
|
|
)
|
|
}
|
|
|
|
return <Component />
|
|
}, [name, config.style])
|
|
|
|
const codeString = React.useMemo(() => {
|
|
if (
|
|
typeof Code?.props["data-rehype-pretty-code-fragment"] !== "undefined"
|
|
) {
|
|
const [Button] = React.Children.toArray(
|
|
Code.props.children
|
|
) as React.ReactElement[]
|
|
return Button?.props?.value || Button?.props?.__rawString__ || null
|
|
}
|
|
}, [Code])
|
|
|
|
if (type === "block") {
|
|
return (
|
|
<div className="relative aspect-[4/2.5] w-full overflow-hidden rounded-md border">
|
|
<Image
|
|
src={`/r/styles/${config.style}/${name}-light.png`}
|
|
alt={name}
|
|
width={1440}
|
|
height={900}
|
|
className="absolute left-0 top-0 z-20 w-[970px] max-w-none bg-background dark:hidden sm:w-[1280px] md:hidden md:dark:hidden"
|
|
/>
|
|
<Image
|
|
src={`/r/styles/${config.style}/${name}-dark.png`}
|
|
alt={name}
|
|
width={1440}
|
|
height={900}
|
|
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
|
/>
|
|
<div className="absolute inset-0 hidden w-[1600px] bg-background md:block">
|
|
<iframe
|
|
src={`/view/styles/${config.style}/${name}`}
|
|
className="size-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn("group relative my-4 flex flex-col space-y-2", className)}
|
|
{...props}
|
|
>
|
|
<Tabs defaultValue="preview" className="relative mr-auto w-full">
|
|
<div className="flex items-center justify-between pb-3">
|
|
{!hideCode && (
|
|
<TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
|
|
<TabsTrigger
|
|
value="preview"
|
|
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
>
|
|
Preview
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="code"
|
|
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
>
|
|
Code
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
)}
|
|
</div>
|
|
<TabsContent value="preview" className="relative rounded-md border">
|
|
<div className="flex items-center justify-between p-4">
|
|
<StyleSwitcher />
|
|
<div className="flex items-center gap-2">
|
|
{description ? <V0Button name={name} /> : null}
|
|
<CopyButton
|
|
value={codeString}
|
|
variant="outline"
|
|
className="h-7 w-7 text-foreground opacity-100 hover:bg-muted hover:text-foreground [&_svg]:h-3.5 [&_svg]:w-3.5"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<ThemeWrapper defaultTheme="zinc">
|
|
<div
|
|
className={cn(
|
|
"preview flex min-h-[350px] w-full justify-center p-10",
|
|
{
|
|
"items-center": align === "center",
|
|
"items-start": align === "start",
|
|
"items-end": align === "end",
|
|
}
|
|
)}
|
|
>
|
|
<React.Suspense
|
|
fallback={
|
|
<div className="flex w-full items-center justify-center text-sm text-muted-foreground">
|
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
|
Loading...
|
|
</div>
|
|
}
|
|
>
|
|
{Preview}
|
|
</React.Suspense>
|
|
</div>
|
|
</ThemeWrapper>
|
|
</TabsContent>
|
|
<TabsContent value="code">
|
|
<div className="flex flex-col space-y-4">
|
|
<div className="w-full rounded-md [&_pre]:my-0 [&_pre]:max-h-[350px] [&_pre]:overflow-auto">
|
|
{Code}
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|