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
52 changes: 52 additions & 0 deletions cloud/app/components/blocks/docs/main-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import LoadingContent from "@/app/components/blocks/loading-content";
// import PagefindMeta from "@/app/components/blocks/pagefind-meta";
import { MDXRenderer } from "@/app/components/mdx/renderer";
import { type DocContent } from "@/app/lib/content/types";

interface MainContentProps {
document: DocContent;
}

/**
* MainContent - Main document content area
*
* Displays the document title, description, and rendered MDX content
*/
const MainContent: React.FC<MainContentProps> = ({ document }) => {
// const path = document.meta.path;
// const pieces = path.split("/");
// const section = pieces.slice(0, 3).join("/");
return (
<div className="px-2 lg:px-4">
<div className="mx-auto w-full max-w-5xl">
<div
id="doc-content"
className="prose prose-sm lg:prose-base prose-slate mdx-container max-w-none overflow-x-auto"
>
{document.mdx ? (
<MDXRenderer className="mdx-content" mdx={document.mdx} />
) : (
<LoadingContent spinnerClassName="h-8 w-8" fullHeight={false} />
)}
{/* {document.mdx ? (
<PagefindMeta
title={document.meta.title}
description={document.meta.description}
section={section}
>
<MDXRenderer
code={document.mdx.code}
frontmatter={document.mdx.frontmatter}
/>
</PagefindMeta>
) : (
<LoadingContent spinnerClassName="h-8 w-8" fullHeight={false} />
)} */}
</div>
</div>
</div>
);
};

export default MainContent;
198 changes: 198 additions & 0 deletions cloud/app/components/blocks/docs/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { docRegistry } from "@/app/lib/content/doc-registry";
import type { DocSpec, SectionSpec } from "@/app/lib/content/spec";
import { type Provider } from "@/app/components/blocks/model-provider-provider";
import Sidebar from "@/app/components/page-sidebar";
import type {
SidebarConfig,
SidebarItem,
SidebarGroup,
SidebarSection,
} from "@/app/components/page-sidebar";
import { docsSpec } from "@/content/docs/_meta";

interface DocsSidebarProps {
selectedProvider?: Provider;
onProviderChange?: (provider: Provider) => void;
}

// No product selector needed in sidebar - now in header

/**
* Helper to convert the spec metadata to the sidebar format
*/
function createSidebarConfig(): SidebarConfig {
// Get all DocInfo objects
const allDocInfo = docRegistry.getAllDocs();

// Create a map from slug pattern to routePath for quick lookup
// Key format: version/section/slug or version/slug for root items
const slugToRoutePathMap: Map<string, string> = new Map();

allDocInfo.forEach((doc) => {
// Extract the slug pattern from the path
const keyPath = doc.path;
slugToRoutePathMap.set(keyPath, doc.routePath);
});

// Get all sections and order them appropriately
const allSections = [...docsSpec.sections];

// Find index section to ensure it appears first
const defaultIndex = allSections.findIndex((s) => s.slug === "index");
if (defaultIndex > 0) {
// Move index section to the front
const defaultSection = allSections.splice(defaultIndex, 1)[0];
allSections.unshift(defaultSection);
}

// Convert doc specs to sidebar items
function convertDocToSidebarItem(
doc: DocSpec,
parentPath: string,
): SidebarItem {
// Construct the logical path for this item (used to look up routePath)
const itemPath = parentPath ? `${parentPath}/${doc.slug}` : doc.slug;

// Look up the routePath from DocInfo if available
const routePath = slugToRoutePathMap.get(itemPath);

// Determine hasContent: explicit value from doc, or default based on children
const hasContent = doc.hasContent ?? !doc.children;

const item: SidebarItem = {
slug: doc.slug,
label: doc.label,
hasContent,
};

// Add routePath if we found a match
if (routePath) {
item.routePath = routePath;
}

// Process children if any
if (doc.children && doc.children.length > 0) {
item.items = {};

doc.children.forEach((childDoc) => {
const childItem = convertDocToSidebarItem(childDoc, itemPath);
if (item.items) {
item.items[childDoc.slug] = childItem;
}
});
}

return item;
}

// Helper to build path prefix for a section (matches logic in spec.ts getDocsFromSpec)
function getSectionPathPrefix(section: SectionSpec): string {
const versionPrefix = section.version || "";
const isDefaultSection = section.slug === "index";
const sectionSlug = isDefaultSection ? "" : section.slug;

if (versionPrefix && sectionSlug) {
return `${versionPrefix}/${sectionSlug}`;
} else if (versionPrefix) {
return versionPrefix;
} else if (sectionSlug) {
return sectionSlug;
}
return "";
}

// Create sidebar sections from spec sections
const sidebarSections: SidebarSection[] = allSections.map((section) => {
const pathPrefix = getSectionPathPrefix(section);

// Create basePath for URL routing
const basePath = pathPrefix ? `/docs/${pathPrefix}` : "/docs";

// Process direct items (those without children) and create groups for top-level folders
const items: Record<string, SidebarItem> = {};
const groups: Record<string, SidebarGroup> = {};

section.children.forEach((child) => {
const hasContent = child.hasContent ?? !child.children;

if (hasContent) {
// This item has content, add it to items (even if it also has children)
items[child.slug] = convertDocToSidebarItem(child, pathPrefix);
} else {
// This is a pure folder (no content), add it as a group
const groupItems: Record<string, SidebarItem> = {};

// Get path for this group's children
const groupPathPrefix = pathPrefix
? `${pathPrefix}/${child.slug}`
: child.slug;

// Process all items in this group
if (child.children) {
child.children.forEach((grandchild) => {
// Convert the grandchild and its descendants to sidebar items
const sidebarItem = convertDocToSidebarItem(
grandchild,
groupPathPrefix,
);
groupItems[grandchild.slug] = sidebarItem;
});
}

// Add the group
groups[child.slug] = {
slug: child.slug,
label: child.label,
items: groupItems,
};
}
});

return {
slug: section.slug,
label: section.label,
basePath,
items,
groups: Object.keys(groups).length > 0 ? groups : undefined,
};
});

// Inject LLM Documentation section
// Use the first section's version for the LLM docs path
// const firstVersion = allSections[0]?.version || "";
// const llmBasePath = firstVersion ? `/docs/${firstVersion}` : "/docs";

// todo(sebastian): add LLM section back in
// const llmItem: SidebarItem = {
// slug: "llms",
// label: "LLMs Text",
// routePath: `${llmBasePath}/llms-full`,
// hasContent: true,
// };
// const llmSection: SidebarSection = {
// slug: "llms",
// label: "LLMs Text",
// basePath: `${llmBasePath}/llms-full`,
// items: { llms: llmItem },
// };

// Add the LLM section to the end
// sidebarSections.push(llmSection);

// Return the complete sidebar config
return {
label: "Documentation",
sections: sidebarSections,
};
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const DocsSidebar = (_props: DocsSidebarProps) => {
// Create sidebar configuration
const sidebarConfig = createSidebarConfig();

// No header content needed since product links are in the main header now
return <Sidebar config={sidebarConfig} />;
};

export default DocsSidebar;
58 changes: 58 additions & 0 deletions cloud/app/components/docs-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from "react";
import PageLayout from "@/app/components/page-layout";
import LoadingContent from "@/app/components/blocks/loading-content";
import { ProviderContextProvider } from "@/app/components/blocks/model-provider-provider";
// import TocSidebar from "@/app/components/toc-sidebar";
import MainContent from "@/app/components/blocks/docs/main-content";
import DocsSidebar from "@/app/components/blocks/docs/sidebar";
import type { DocContent } from "@/app/lib/content/types";

type DocsPageProps = {
document?: DocContent;
isLoading?: boolean;
};

/**
* DocsPage component - Top-level documentation page component
*
* Handles metadata, layout and content rendering for all documentation pages
* Supports both loaded and loading states
*/
const DocsPage: React.FC<DocsPageProps> = ({ document, isLoading = false }) => {
return (
<>
<ProviderContextProvider>
<PageLayout>
<PageLayout.LeftSidebar className="pt-1" collapsible={true}>
<DocsSidebar />
</PageLayout.LeftSidebar>

<PageLayout.Content>
{isLoading ? (
<LoadingContent fullHeight={true} />
) : (
document && <MainContent document={document} />
)}
</PageLayout.Content>

<PageLayout.RightSidebar
className="pt-1"
mobileCollapsible={true}
mobileTitle="On this page"
>
Sidebar content
{/* {isLoading ? (
<div className="h-full">
<div className="bg-muted mx-4 mt-16 h-6 animate-pulse rounded-md"></div>
</div>
) : (
document && <TocSidebar document={document} />
)} */}
</PageLayout.RightSidebar>
</PageLayout>
</ProviderContextProvider>
</>
);
};

export default DocsPage;
8 changes: 4 additions & 4 deletions cloud/app/components/home-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { cn } from "@/app/lib/utils";
import { useSunsetTime } from "@/app/hooks/sunset-time";
import { useGradientFadeOnScroll } from "@/app/hooks/gradient-fade-scroll";
import { BookOpen, ChevronDown, ChevronUp, Rocket, Users } from "lucide-react";
import { BookOpen, ChevronDown, ChevronUp, Users } from "lucide-react";
import { ButtonLink } from "@/app/components/ui/button-link";
import homeStyles from "@/app/components/home-page.module.css";
import { ResponsiveTextBlock } from "@/app/components/blocks/responsive-text-block";
Expand Down Expand Up @@ -149,13 +149,13 @@ function HeroBlock({ onScrollDown, showScrollButton }: HeroBlockProps) {
</div>
<div className="mt-8 flex w-full max-w-3xl flex-col items-center justify-center gap-4 sm:flex-row">
<ButtonLink
href="/docs/mirascope/v2"
href="/docs/v1"
variant="outline"
size="lg"
className="box-shade w-full min-w-[200px] border-0 bg-white text-center font-handwriting font-bold text-black hover:bg-white/90 hover:text-black sm:w-auto"
>
<Rocket className="size-5" aria-hidden="true" />
Mirascope v2 Alpha
<BookOpen className="size-5" aria-hidden="true" />
Mirascope v1
</ButtonLink>
<ButtonLink
href="/discord-invite"
Expand Down
2 changes: 1 addition & 1 deletion cloud/app/components/page-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ const SectionTab = ({
params?: Record<string, unknown>;
children: React.ReactNode;
}) => {
const activeClass = `bg-button-primary text-white font-medium`;
const activeClass = `bg-primary text-white font-medium`;
const inactiveClass = `text-muted-foreground hover:bg-muted hover:text-muted-foreground`;

return (
Expand Down
Loading