From 9e3a529473584e74bd18af20e367cd42755053ea Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Thu, 18 Sep 2025 01:15:46 -0400 Subject: [PATCH 1/2] fix: wip --- apps/www/app/(app)/blocks/page.tsx | 34 ++ apps/www/app/(blog)/blog/page.tsx | 4 +- apps/www/components/block-display.tsx | 69 +++ apps/www/components/block-viewer.tsx | 464 ++++++++++++++++++ .../www/components/component-preview-tabs.tsx | 8 +- apps/www/components/open-in-v0-button.tsx | 35 +- apps/www/components/pro-component-preview.tsx | 32 ++ apps/www/components/ui/resizable-preview.tsx | 82 ++++ apps/www/components/ui/resizable.tsx | 45 ++ apps/www/components/ui/toggle-group.tsx | 61 +++ apps/www/components/ui/toggle.tsx | 47 ++ apps/www/config/docs.ts | 13 + apps/www/content/docs/blocks/pricing.mdx | 46 ++ apps/www/lib/events.ts | 4 +- apps/www/mdx-components.tsx | 12 +- apps/www/package.json | 5 +- pnpm-lock.yaml | 76 ++- 17 files changed, 1001 insertions(+), 36 deletions(-) create mode 100644 apps/www/app/(app)/blocks/page.tsx create mode 100644 apps/www/components/block-display.tsx create mode 100644 apps/www/components/block-viewer.tsx create mode 100644 apps/www/components/pro-component-preview.tsx create mode 100644 apps/www/components/ui/resizable-preview.tsx create mode 100644 apps/www/components/ui/resizable.tsx create mode 100644 apps/www/components/ui/toggle-group.tsx create mode 100644 apps/www/components/ui/toggle.tsx create mode 100644 apps/www/content/docs/blocks/pricing.mdx diff --git a/apps/www/app/(app)/blocks/page.tsx b/apps/www/app/(app)/blocks/page.tsx new file mode 100644 index 000000000..5b05bf862 --- /dev/null +++ b/apps/www/app/(app)/blocks/page.tsx @@ -0,0 +1,34 @@ +import Link from "next/link"; + +import { BlockDisplay } from "@/components/block-display"; +import { Button } from "@/components/ui/button"; + +export const dynamic = "force-static"; +export const revalidate = false; + +const FEATURED_BLOCKS = [ + "footer-1", + "faq-3", + // "dashboard-01", + // "sidebar-07", + // "sidebar-03", + // "login-03", + // "login-04", +]; + +export default async function BlocksPage() { + return ( +
+ {FEATURED_BLOCKS.map((name) => ( + + ))} +
+
+ +
+
+
+ ); +} diff --git a/apps/www/app/(blog)/blog/page.tsx b/apps/www/app/(blog)/blog/page.tsx index e82c806dd..6fceb88e0 100644 --- a/apps/www/app/(blog)/blog/page.tsx +++ b/apps/www/app/(blog)/blog/page.tsx @@ -30,8 +30,8 @@ export default async function Page({ const selectedTag = params?.tag ?? ""; const posts = blogSource.getPages().sort((a, b) => { - const dateA = new Date(a.data?.publishedOn || 0).getTime(); - const dateB = new Date(b.data?.publishedOn || 0).getTime(); + const dateA = new Date(a.data?.publishedOn).getTime(); + const dateB = new Date(b.data?.publishedOn).getTime(); return dateB - dateA; }); diff --git a/apps/www/components/block-display.tsx b/apps/www/components/block-display.tsx new file mode 100644 index 000000000..5bc6d2086 --- /dev/null +++ b/apps/www/components/block-display.tsx @@ -0,0 +1,69 @@ +import * as React from "react"; +import { registryItemFileSchema } from "shadcn/schema"; +import { z } from "zod"; + +import { BlockViewer } from "@/components/block-viewer"; +import { ComponentPreview } from "@/components/component-preview"; +import { highlightCode } from "@/lib/highlight-code"; +import { + createFileTreeForRegistryItemFiles, + getRegistryItem, +} from "@/lib/registry"; +import { cn } from "@/lib/utils"; + +export async function BlockDisplay({ name }: { name: string }) { + // const item = await getCachedRegistryItem(name); + + // if (!item?.files) { + // return null; + // } + + // const [tree, highlightedFiles] = await Promise.all([ + // getCachedFileTree(item.files), + // getCachedHighlightedFiles(item.files), + // ]); + + return ( + + .p-6]:p-0", + // item.meta?.containerClassName, + )} + /> + + ); +} + +const getCachedRegistryItem = React.cache(async (name: string) => { + return await getRegistryItem(name); +}); + +const getCachedFileTree = React.cache( + async (files: Array<{ path: string; target?: string }>) => { + if (!files) { + return null; + } + + return await createFileTreeForRegistryItemFiles(files); + }, +); + +const getCachedHighlightedFiles = React.cache( + async (files: z.infer[]) => { + return await Promise.all( + files.map(async (file) => ({ + ...file, + highlightedContent: await highlightCode(file.content ?? ""), + })), + ); + }, +); diff --git a/apps/www/components/block-viewer.tsx b/apps/www/components/block-viewer.tsx new file mode 100644 index 000000000..ae6f04d5b --- /dev/null +++ b/apps/www/components/block-viewer.tsx @@ -0,0 +1,464 @@ +"use client"; + +import { + Check, + ChevronRight, + Clipboard, + File, + Folder, + Fullscreen, + Monitor, + RotateCw, + Smartphone, + Tablet, + Terminal, +} from "lucide-react"; +import Link from "next/link"; +import * as React from "react"; +import { ImperativePanelHandle } from "react-resizable-panels"; +import { registryItemFileSchema, registryItemSchema } from "shadcn/schema"; +import { z } from "zod"; + +import { getIconForLanguageExtension } from "@/components/icons"; +import { OpenInV0Button } from "@/components/open-in-v0-button"; +import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable"; +import { Separator } from "@/components/ui/separator"; +import { + Sidebar, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarProvider, +} from "@/components/ui/sidebar"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; +import { trackEvent } from "@/lib/events"; +import { createFileTreeForRegistryItemFiles, FileTree } from "@/lib/registry"; +import { cn } from "@/lib/utils"; + +type BlockViewerContext = { + name: string; + + item: z.infer; + view: "code" | "preview"; + setView: (view: "code" | "preview") => void; + activeFile: string | null; + setActiveFile: (file: string) => void; + resizablePanelRef: React.RefObject | null; + tree: ReturnType | null; + highlightedFiles: + | (z.infer & { + highlightedContent: string; + })[] + | null; + iframeKey?: number; + setIframeKey?: React.Dispatch>; +}; + +const BlockViewerContext = React.createContext(null); + +function useBlockViewer() { + const context = React.useContext(BlockViewerContext); + if (!context) { + throw new Error( + "useBlockViewer must be used within a BlockViewerProvider.", + ); + } + return context; +} + +function BlockViewerProvider({ + item, + tree, + name, + highlightedFiles, + children, +}: Pick & { + children: React.ReactNode; +}) { + const [view, setView] = React.useState("preview"); + const [activeFile, setActiveFile] = React.useState< + BlockViewerContext["activeFile"] + >(highlightedFiles?.[0].target ?? null); + const resizablePanelRef = React.useRef(null); + const [iframeKey, setIframeKey] = React.useState(0); + + return ( + +
+ {children} +
+
+ ); +} + +function BlockViewerToolbar() { + const { setView, view, item, resizablePanelRef, setIframeKey, name } = + useBlockViewer(); + const { copyToClipboard, isCopied } = useCopyToClipboard(); + + return ( +
+ setView(value as "preview" | "code")} + > + + Preview + Code + + + + + {/* {item.description?.replace(/\.$/, "")} */} + +
+
+ { + setView("preview"); + if (resizablePanelRef?.current) { + resizablePanelRef.current.resize(parseInt(value)); + } + }} + className="gap-1 *:data-[slot=toggle-group-item]:!size-6 *:data-[slot=toggle-group-item]:!rounded-sm" + > + + + + + + + + + + + + + + +
+ + + + +
+
+ ); +} + +function BlockViewerIframe({ className }: { className?: string }) { + const { iframeKey, name } = useBlockViewer(); + + return ( +