Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions apps/www/app/(home)/blocks/page.tsx

This file was deleted.

59 changes: 0 additions & 59 deletions apps/www/app/(view)/view/[name]/page.tsx

This file was deleted.

131 changes: 131 additions & 0 deletions apps/www/app/blocks/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { Metadata } from "next"

import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock"
import { DocsLayout } from "fumadocs-ui/layouts/docs"
import { notFound } from "next/navigation"

import { BlockInfoPane } from "@/components/blocks/block-info-pane"
import { BlockPreviewPane } from "@/components/blocks/block-preview-pane"
import { getMDXComponents } from "@/components/mdx-components"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import { getBlockShowcase } from "@/lib/block-showcase"
import { blocksSource } from "@/lib/blocks-source"

export const revalidate = false
export const dynamic = "force-static"
export const dynamicParams = false

type BlockPageProps = {
params: Promise<{ slug?: string[] }>
}

export default async function BlockPage(props: BlockPageProps) {
const params = await props.params
const slug = params.slug ?? []
const page = blocksSource.getPage(slug)

if (!page) {
notFound()
}
Comment on lines +27 to +33
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slug defaults to [], which means the route /blocks will call blocksSource.getPage([]) and likely 404 unless there is an index page in content/docs/blocks. If /blocks is intended to be a valid landing page, handle the empty-slug case explicitly (redirect to a default block or render an index/list page).

Copilot uses AI. Check for mistakes.

const showcase = getBlockShowcase(page.data.preview)

const MDXContent = page.data.body
const PreviewComponent = showcase.component
const content = (
Comment on lines +35 to +39
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBlockShowcase(page.data.preview) is used without validating the return value; if a block’s frontmatter preview doesn’t exist in the showcase registry, showcase.component will throw. Add a guard (and likely notFound() or a fallback) when no showcase is registered for the page.

Copilot uses AI. Check for mistakes.
<MDXContent
components={getMDXComponents({
pre: ({ ref: _ref, ...props }) => (
<CodeBlock {...props} keepBackground>
<Pre>{props.children}</Pre>
</CodeBlock>
),
})}
/>
)

return (
<div className="w-full">
<DocsLayout
sidebar={{
enabled: false,
}}
tree={blocksSource.getPageTree()}
>
<main className="relative min-h-screen bg-background">
<div className="flex h-full w-full">
<div
className={`
w-full
lg:hidden
`}
>
<BlockPreviewPane>
<PreviewComponent />
</BlockPreviewPane>
<BlockInfoPane
content={content}
description={page.data.description}
title={page.data.title}
url={page.url}
/>
</div>

<div
className={`
hidden h-screen w-full
lg:block
`}
>
<ResizablePanelGroup>
<ResizablePanel defaultSize="35%">
<BlockInfoPane
content={content}
description={page.data.description}
title={page.data.title}
url={page.url}
/>
</ResizablePanel>
<ResizableHandle className="z-20" withHandle />
<ResizablePanel defaultSize="65%">
<BlockPreviewPane>
<PreviewComponent />
</BlockPreviewPane>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</div>
</main>
</DocsLayout>
</div>
)
}

export async function generateMetadata(
props: BlockPageProps
): Promise<Metadata> {
const params = await props.params

if (!params.slug?.length) {
return {
title: "Blocks",
}
}

const page = blocksSource.getPage(params.slug)

if (!page) notFound()

return {
description: page.data.description,
title: `${page.data.title} Block`,
}
}

export function generateStaticParams() {
return blocksSource.generateParams()
}
7 changes: 7 additions & 0 deletions apps/www/app/blocks/blocks.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import "tailwindcss";
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocks.css re-imports tailwindcss even though app/global.css already imports it. This will generate duplicate base/utilities CSS (and can affect ordering). Consider removing the Tailwind import here and only importing the fumadocs CSS/theme overrides.

Suggested change
@import "tailwindcss";

Copilot uses AI. Check for mistakes.
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";

@theme {
--fd-layout-width: 0;
}
39 changes: 39 additions & 0 deletions apps/www/app/blocks/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { ReactNode } from "react"

import { RootProvider } from "fumadocs-ui/provider/next"

import { BlocksSidebar } from "@/components/blocks/blocks-sidebar"
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
import { blocksSource } from "@/lib/blocks-source"

import "./blocks.css"

export default function BlocksLayout({ children }: { children: ReactNode }) {
const items = blocksSource.getPages().map((page) => ({
description: page.data.description,
href: page.url,
status: page.data.status,
title: page.data.title,
}))

return (
<RootProvider
theme={{
forcedTheme: "light",
}}
>
<SidebarProvider
defaultOpen={false}
style={
{
"--sidebar-width": "20rem",
"--sidebar-width-mobile": "20rem",
} as React.CSSProperties
}
>
<BlocksSidebar items={items} />
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>
</RootProvider>
)
}
2 changes: 1 addition & 1 deletion apps/www/app/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactNode } from "react"

import "@/app/docs/docs.css"
import "./docs.css"
import { DocsLayout } from "fumadocs-ui/layouts/docs"
import { RootProvider } from "fumadocs-ui/provider/next"

Expand Down
4 changes: 3 additions & 1 deletion apps/www/app/global.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "./docs/docs.css";

@import "./shadcn.css";
@import "./limeplay.css";

@custom-variant dark (&:is(.dark *));

:root {
--page-padding-inline: 1rem;
--page-padding: max(env(safe-area-inset-left), var(--page-padding-inline))
Expand Down
2 changes: 0 additions & 2 deletions apps/www/app/limeplay.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@import "tailwindcss";

:root {
--lp-timeline-track-height: 4px;
--lp-timeline-track-height-active: 7px;
Expand Down
3 changes: 0 additions & 3 deletions apps/www/app/shadcn.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
Expand Down
5 changes: 2 additions & 3 deletions apps/www/components/block-display.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { registryItemFileSchema } from "shadcn/schema"
import type { z } from "zod"
import type { RegistryItem } from "shadcn/schema"

import { highlight } from "fumadocs-core/highlight"
import * as React from "react"
Expand Down Expand Up @@ -46,7 +45,7 @@ const getCachedFileTree = React.cache(
)

const getCachedHighlightedFiles = React.cache(
async (files: z.infer<typeof registryItemFileSchema>[]) => {
async (files: NonNullable<RegistryItem["files"]>) => {
return await Promise.all(
files.map(async (file) => ({
...file,
Expand Down
18 changes: 8 additions & 10 deletions apps/www/components/block-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client"

import type { ImperativePanelHandle } from "react-resizable-panels"
import type { registryItemFileSchema, registryItemSchema } from "shadcn/schema"
import type { z } from "zod"
import type { PanelImperativeHandle } from "react-resizable-panels"
import type { RegistryItem } from "shadcn/schema"

import {
Check,
Expand Down Expand Up @@ -59,13 +58,13 @@ import { cn } from "@/lib/utils"
interface BlockViewerContext {
activeFile: null | string
highlightedFiles:
| (z.infer<typeof registryItemFileSchema> & {
| (NonNullable<RegistryItem["files"]>[number] & {
highlightedContent: any
})[]
| null
iframeKey?: number
item: z.infer<typeof registryItemSchema>
resizablePanelRef: null | React.RefObject<ImperativePanelHandle | null>
item: RegistryItem
resizablePanelRef: null | React.RefObject<null | PanelImperativeHandle>
setActiveFile: (file: string) => void
setIframeKey?: React.Dispatch<React.SetStateAction<number>>
setView: (view: "code" | "preview") => void
Expand Down Expand Up @@ -147,7 +146,7 @@ function BlockViewerCode() {
md:h-(--height)
`}
>
<ResizablePanelGroup className="w-full" direction="horizontal">
<ResizablePanelGroup className="w-full">
<ResizablePanel defaultSize={25} maxSize={40} minSize={15}>
<BlockViewerFileTree />
</ResizablePanel>
Expand Down Expand Up @@ -260,7 +259,7 @@ function BlockViewerProvider({
const [activeFile, setActiveFile] = React.useState<
BlockViewerContext["activeFile"]
>(highlightedFiles?.[0].target ?? null)
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null)
const [iframeKey, setIframeKey] = React.useState(0)

return (
Expand Down Expand Up @@ -455,7 +454,6 @@ function BlockViewerView() {
relative z-10
after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl after:bg-background/50
`}
direction="horizontal"
>
<ResizablePanel
className={`
Expand All @@ -464,7 +462,7 @@ function BlockViewerView() {
`}
defaultSize={100}
minSize={30}
ref={resizablePanelRef as React.Ref<ImperativePanelHandle>}
panelRef={resizablePanelRef as React.Ref<PanelImperativeHandle>}
>
<BlockViewerIframe />
</ResizablePanel>
Expand Down
Loading