@@ -27,10 +27,12 @@ import Link from "next/link"
2727
2828import { Index } from "@/__registry__"
2929import { getIconForLanguageExtension } from "@/components/icons"
30+ import { siteConfig } from "@/config/site"
3031import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
3132import { trackEvent } from "@/lib/events"
33+ import { cn } from "@/lib/utils"
3234
33- import { Button } from "./ui/button"
35+ import { Button , buttonVariants } from "./ui/button"
3436import { ResizableHandle , ResizablePanel , ResizablePanelGroup } from "./ui/resizable"
3537import { Sidebar , SidebarGroup , SidebarGroupContent , SidebarGroupLabel , SidebarMenu , SidebarMenuButton , SidebarMenuItem , SidebarMenuSub , SidebarProvider } from "./ui/sidebar"
3638import { Tabs , TabsList , TabsTrigger } from "./ui/tabs"
@@ -49,6 +51,7 @@ type BlockViewerContext = {
4951 view : "code" | "preview"
5052 setActiveFile : ( file : string ) => void
5153 setView : ( view : "code" | "preview" ) => void
54+ blocks ?: boolean
5255 iframeKey ?: number
5356 setIframeKey ?: React . Dispatch < React . SetStateAction < number > >
5457}
@@ -64,11 +67,12 @@ function useBlockViewer() {
6467}
6568
6669function BlockViewerProvider ( {
70+ blocks,
6771 children,
6872 highlightedFiles,
6973 item,
7074 tree,
71- } : Pick < BlockViewerContext , "highlightedFiles" | "item" | "tree" > & {
75+ } : Pick < BlockViewerContext , "blocks" | " highlightedFiles" | "item" | "tree" > & {
7276 children : React . ReactNode
7377} ) {
7478 const [ view , setView ] = React . useState < BlockViewerContext [ "view" ] > ( "preview" )
@@ -82,6 +86,7 @@ function BlockViewerProvider({
8286 < BlockViewerContext . Provider
8387 value = { {
8488 activeFile,
89+ blocks,
8590 highlightedFiles,
8691 iframeKey,
8792 item,
@@ -110,111 +115,156 @@ function BlockViewerProvider({
110115}
111116
112117function BlockViewerToolbar ( ) {
113- const { item, resizablePanelRef, setIframeKey, setView, view } =
118+ const { blocks , item, resizablePanelRef, setIframeKey, setView, view } =
114119 useBlockViewer ( )
120+
121+
115122 const { copyToClipboard, isCopied } = useCopyToClipboard ( )
116123
124+ const isPro = item ?. meta ?. isPro
125+
117126 return (
118- < div className = "hidden w-full items-center gap-2 pl-2 md:pr-6 lg:flex" >
127+ < div className = "hidden w-full items-center gap-2 pl-2 py-1 md:pr-6 lg:flex" >
119128 < Tabs
120129 value = { view }
121130 onValueChange = { ( value ) => setView ( value as "code" | "preview" ) }
122131 >
123- < TabsList className = "grid h-8 grid-cols-2 items-center rounded-md p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs" >
132+ < TabsList className = { cn ( "grid h-8 items-center rounded-md p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs" ,
133+ isPro ? "grid-cols-1" : "grid-cols-2"
134+ ) } >
124135 < TabsTrigger value = "preview" > Preview</ TabsTrigger >
125- < TabsTrigger value = "code" > Code</ TabsTrigger >
136+ { ! isPro && < TabsTrigger value = "code" > Code</ TabsTrigger > }
126137 </ TabsList >
127138 </ Tabs >
128139 < Separator orientation = "vertical" className = "mx-2 !h-4" />
129- < a
130- className = "flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
131- href = { `#${ item . name } ` }
132- >
133- { item . description ?. replace ( / \. $ / , "" ) }
134- </ a >
135- < div className = "ml-auto flex items-center gap-2" >
136- < div className = "h-8 items-center gap-1.5 rounded-md border p-1 shadow-none" >
137- < ToggleGroup
138- className = "gap-1 *:data-[slot=toggle-group-item]:!size-6 *:data-[slot=toggle-group-item]:!rounded-sm"
139- defaultValue = "100"
140- onValueChange = { ( value ) => {
141- setView ( "preview" )
142- if ( resizablePanelRef ?. current ) {
143- resizablePanelRef . current . resize ( Number . parseInt ( value ) )
144- }
145- } }
146- type = "single"
140+
141+ {
142+ blocks && (
143+ < a
144+ className = "flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
145+ href = { `#${ item . name } ` }
147146 >
148- < ToggleGroupItem value = "100" title = "Desktop" >
149- < Monitor />
150- </ ToggleGroupItem >
151- < ToggleGroupItem value = "60" title = "Tablet" >
152- < Tablet />
153- </ ToggleGroupItem >
154- < ToggleGroupItem value = "30" title = "Mobile" >
155- < Smartphone />
156- </ ToggleGroupItem >
157- < Separator orientation = "vertical" className = "!h-4" />
158- < Button
159- asChild
160- size = "icon"
161- variant = "ghost"
162- className = "size-6 rounded-sm p-0"
163- title = "Open in New Tab"
164- >
165- < Link href = { `/blocks/${ item . name } ` } target = "_blank" >
166- < span className = "sr-only" > Open in New Tab</ span >
167- < Fullscreen />
168- </ Link >
169- </ Button >
170- < Separator orientation = "vertical" className = "!h-4" />
171- < Button
172- size = "icon"
173- variant = "ghost"
174- className = "size-6 rounded-sm p-0"
175- onClick = { ( ) => {
176- if ( setIframeKey ) {
177- setIframeKey ( ( k ) => k + 1 )
178- }
179- } }
180- title = "Refresh Preview"
181- >
182- < RotateCw />
183- < span className = "sr-only" > Refresh Preview</ span >
184- </ Button >
185- </ ToggleGroup >
186- </ div >
187- < Separator orientation = "vertical" className = "mx-1 !h-4" />
188- < Button
189- size = "sm"
190- variant = "outline"
191- className = "w-fit gap-1 px-2 shadow-none"
192- onClick = { ( ) => {
193- copyToClipboard ( `npx shadcn@latest add ${ item . name } ` )
194- } }
147+ { item . description ?. replace ( / \. $ / , "" ) }
148+ </ a >
149+ )
150+ }
151+
152+ { isPro ? (
153+ < Link
154+ className = { cn (
155+ buttonVariants ( ) ,
156+ 'group relative flex justify-start gap-2 overflow-hidden rounded-sm whitespace-pre' ,
157+ 'dark:bg-muted dark:text-foreground' ,
158+ 'hover:ring-2 hover:ring-primary hover:ring-offset-2' ,
159+ 'transition-all duration-300 ease-out' ,
160+ 'h-[26px] px-2 text-xs'
161+ ) }
162+ href = { item . meta ?. descriptionSrc ?? siteConfig . links . potionIframe }
163+ target = "_blank"
195164 >
196- { isCopied ? < Check /> : < Terminal /> }
197- < span > npx shadcn@latest add { item . name } </ span >
198- </ Button >
199- < Separator orientation = "vertical" className = "mx-1 !h-4" />
200- </ div >
165+ < span
166+ className = { cn (
167+ 'absolute right-0 -mt-12 h-32 w-8 translate-x-12 rotate-12' ,
168+ 'bg-white opacity-10' ,
169+ 'transition-all duration-1000 ease-out'
170+ ) }
171+ />
172+ Get the code
173+ </ Link >
174+ ) :
175+ (
176+ < div className = "ml-auto flex items-center gap-2" >
177+ {
178+ blocks && (
179+ < React . Fragment >
180+ < div className = "h-8 items-center gap-1.5 rounded-md border p-1 shadow-none" >
181+ < ToggleGroup
182+ className = "gap-1 *:data-[slot=toggle-group-item]:!size-6 *:data-[slot=toggle-group-item]:!rounded-sm"
183+ defaultValue = "100"
184+ onValueChange = { ( value ) => {
185+ setView ( "preview" )
186+ if ( resizablePanelRef ?. current ) {
187+ resizablePanelRef . current . resize ( Number . parseInt ( value ) )
188+ }
189+ } }
190+ type = "single"
191+ >
192+ < ToggleGroupItem value = "100" title = "Desktop" >
193+ < Monitor />
194+ </ ToggleGroupItem >
195+ < ToggleGroupItem value = "60" title = "Tablet" >
196+ < Tablet />
197+ </ ToggleGroupItem >
198+ < ToggleGroupItem value = "30" title = "Mobile" >
199+ < Smartphone />
200+ </ ToggleGroupItem >
201+ < Separator orientation = "vertical" className = "!h-4" />
202+ < Button
203+ asChild
204+ size = "icon"
205+ variant = "ghost"
206+ className = "size-6 rounded-sm p-0"
207+ title = "Open in New Tab"
208+ >
209+ < Link href = { `/blocks/${ item . name } ` } target = "_blank" >
210+ < span className = "sr-only" > Open in New Tab</ span >
211+ < Fullscreen />
212+ </ Link >
213+ </ Button >
214+ < Separator orientation = "vertical" className = "!h-4" />
215+ < Button
216+ size = "icon"
217+ variant = "ghost"
218+ className = "size-6 rounded-sm p-0"
219+ onClick = { ( ) => {
220+ if ( setIframeKey ) {
221+ setIframeKey ( ( k ) => k + 1 )
222+ }
223+ } }
224+ title = "Refresh Preview"
225+ >
226+ < RotateCw />
227+ < span className = "sr-only" > Refresh Preview</ span >
228+ </ Button >
229+ </ ToggleGroup >
230+ </ div >
231+ < Separator orientation = "vertical" className = "mx-1 !h-4" />
232+
233+ < Button
234+ size = "sm"
235+ variant = "outline"
236+ className = "w-fit gap-1 px-2 shadow-none"
237+ onClick = { ( ) => {
238+ copyToClipboard ( `npx shadcn@latest add ${ item . name } ` )
239+ } }
240+ >
241+ { isCopied ? < Check /> : < Terminal /> }
242+ < span > npx shadcn@latest add { item . name } </ span >
243+ </ Button >
244+ < Separator orientation = "vertical" className = "mx-1 !h-4" />
245+ </ React . Fragment >
246+ )
247+ }
248+
249+
250+ </ div >
251+ )
252+ }
253+
201254 </ div >
202255 )
203256}
204257
205258function BlockViewerIframe ( ) {
206259 const { iframeKey, item } = useBlockViewer ( )
207260
208- console . log ( "🚀 ~ BlockViewerIframe ~ item:" , item )
209-
210-
211261 const Preview = React . useMemo ( ( ) => {
212262
213- if ( item . meta ?. isPro ) return null
263+ if ( item . meta ?. isPro ) return null
214264
215265 const Component = Index [ item . name ] ?. component ;
216266
217- if ( ! Component ) {
267+ if ( ! Component ) {
218268 return (
219269 < p className = "text-sm text-muted-foreground" >
220270 Component{ ' ' }
@@ -484,6 +534,7 @@ function BlockViewer({
484534} ) {
485535 return (
486536 < BlockViewerProvider
537+ blocks = { blocks }
487538 highlightedFiles = { highlightedFiles }
488539 item = { item }
489540 tree = { tree }
0 commit comments