From 529637b8a9bd3a2dc7df714840f1efd3efee66a0 Mon Sep 17 00:00:00 2001 From: Tim Delisle Date: Fri, 14 Nov 2025 21:24:25 -0800 Subject: [PATCH 01/10] fixed templates page --- apps/framework-docs-v2/.npmrc | 20 +- .../content/templates/index.mdx | 60 +--- apps/framework-docs-v2/package.json | 2 + .../src/app/[...slug]/page.tsx | 12 +- .../strategy/platform-engineering/page.tsx | 66 ++++ .../src/app/templates/layout.tsx | 31 ++ .../src/app/templates/page.tsx | 62 ++++ .../src/app/templates/templates-side-nav.tsx | 296 ++++++++++++++++++ .../src/components/mdx-renderer.tsx | 2 + .../src/components/mdx/command-snippet.tsx | 42 +++ .../src/components/mdx/index.ts | 1 + .../src/components/mdx/template-card.tsx | 155 +++++---- .../src/components/mdx/template-grid.tsx | 159 ++-------- .../src/components/ui/checkbox.tsx | 30 ++ .../src/components/ui/command.tsx | 2 +- .../src/components/ui/input.tsx | 2 +- .../src/components/ui/item.tsx | 164 ++++++++++ .../src/components/ui/select.tsx | 2 +- apps/framework-docs-v2/src/lib/content.ts | 12 +- apps/framework-docs-v2/src/styles/globals.css | 4 +- pnpm-lock.yaml | 93 +++++- 21 files changed, 937 insertions(+), 280 deletions(-) create mode 100644 apps/framework-docs-v2/src/app/guides/strategy/platform-engineering/page.tsx create mode 100644 apps/framework-docs-v2/src/app/templates/layout.tsx create mode 100644 apps/framework-docs-v2/src/app/templates/page.tsx create mode 100644 apps/framework-docs-v2/src/app/templates/templates-side-nav.tsx create mode 100644 apps/framework-docs-v2/src/components/mdx/command-snippet.tsx create mode 100644 apps/framework-docs-v2/src/components/ui/checkbox.tsx create mode 100644 apps/framework-docs-v2/src/components/ui/item.tsx diff --git a/apps/framework-docs-v2/.npmrc b/apps/framework-docs-v2/.npmrc index afab184d37..be313dafb4 100644 --- a/apps/framework-docs-v2/.npmrc +++ b/apps/framework-docs-v2/.npmrc @@ -1,6 +1,20 @@ # Force all dependencies to be hoisted locally to this app's node_modules # This prevents TypeScript from finding React types in nested node_modules -# This overrides the root .npmrc which prevents hoisting to support multiple React versions -# Since this app only uses React 19, we can safely hoist everything here -shamefully-hoist=true +# This works with the root .npmrc which prevents React from being hoisted to root +# Since this app only uses React 19, we can safely hoist everything locally here +# +# IMPORTANT: We use hoist-pattern instead of shamefully-hoist=true to avoid +# conflicts with the root hoisting pattern when running pnpm add from this directory +# (e.g., via shadcn CLI). This hoists everything locally without modifying root structure. +hoist-pattern[]=* + +# Match root public-hoist-pattern to prevent ERR_PNPM_PUBLIC_HOIST_PATTERN_DIFF +# This ensures compatibility when running pnpm add from this directory +public-hoist-pattern[]=!react +public-hoist-pattern[]=!react-dom +public-hoist-pattern[]=!react/jsx-runtime +public-hoist-pattern[]=!react-dom/server +public-hoist-pattern[]=!react/jsx-dev-runtime +public-hoist-pattern[]=!@types/react +public-hoist-pattern[]=!@types/react-dom diff --git a/apps/framework-docs-v2/content/templates/index.mdx b/apps/framework-docs-v2/content/templates/index.mdx index 29cdba52cf..149114ccc2 100644 --- a/apps/framework-docs-v2/content/templates/index.mdx +++ b/apps/framework-docs-v2/content/templates/index.mdx @@ -5,69 +5,13 @@ order: 2 category: getting-started --- -import { CTACards, CTACard } from "@/components/mdx"; -import { Badge } from "@/components/ui/badge"; -import Link from "next/link"; -import { TemplatesGridServer } from "@/components/mdx"; +import { TemplatesGridServer, CommandSnippet } from "@/components/mdx"; # Templates & Apps Moose provides two ways to get started: **templates** and **demo apps**. Templates are simple skeleton applications that you can initialize with `moose init`, while demo apps are more advanced examples available on GitHub that showcase real-world use cases and integrations. -**Initialize a template:** -```bash filename="Terminal" copy -moose init PROJECT_NAME TEMPLATE_NAME -``` - -**List available templates:** -```bash filename="Terminal" copy -moose template list -``` - -## Popular Apps - - - - - - - - - - ---- + ## Browse Apps and Templates diff --git a/apps/framework-docs-v2/package.json b/apps/framework-docs-v2/package.json index ebb96fdf62..9780a46f10 100644 --- a/apps/framework-docs-v2/package.json +++ b/apps/framework-docs-v2/package.json @@ -21,11 +21,13 @@ "@next/mdx": "^16.0.1", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.1.7", diff --git a/apps/framework-docs-v2/src/app/[...slug]/page.tsx b/apps/framework-docs-v2/src/app/[...slug]/page.tsx index 35a58dc1d6..cfb3523efc 100644 --- a/apps/framework-docs-v2/src/app/[...slug]/page.tsx +++ b/apps/framework-docs-v2/src/app/[...slug]/page.tsx @@ -19,17 +19,18 @@ export async function generateStaticParams() { const slugs = getAllSlugs(); // Generate params for each slug + // Note: templates is excluded from getAllSlugs() as it is now an explicit page const allParams: { slug: string[] }[] = slugs.map((slug) => ({ slug: slug.split("/"), })); - // Also add section index routes (moosestack, ai, hosting, templates) - // These map to section/index.mdx files + // Also add section index routes (moosestack, ai, hosting, guides) + // Note: templates is now an explicit page, so it's excluded here allParams.push( { slug: ["moosestack"] }, { slug: ["ai"] }, { slug: ["hosting"] }, - { slug: ["templates"] }, + { slug: ["guides"] }, ); return allParams; @@ -81,6 +82,11 @@ export default async function DocPage({ params }: PageProps) { const slug = slugArray.join("/"); + // Templates is now an explicit page, so it should not be handled by this catch-all route + if (slug.startsWith("templates/")) { + notFound(); + } + let content; try { content = await parseMarkdownContent(slug); diff --git a/apps/framework-docs-v2/src/app/guides/strategy/platform-engineering/page.tsx b/apps/framework-docs-v2/src/app/guides/strategy/platform-engineering/page.tsx new file mode 100644 index 0000000000..9439ecb4e1 --- /dev/null +++ b/apps/framework-docs-v2/src/app/guides/strategy/platform-engineering/page.tsx @@ -0,0 +1,66 @@ +import { notFound } from "next/navigation"; +import type { Metadata } from "next"; +import { parseMarkdownContent } from "@/lib/content"; +import { TOCNav } from "@/components/navigation/toc-nav"; +import { MDXRenderer } from "@/components/mdx-renderer"; +import { DocBreadcrumbs } from "@/components/navigation/doc-breadcrumbs"; +import { buildDocBreadcrumbs } from "@/lib/breadcrumbs"; + +export const dynamic = "force-dynamic"; + +export async function generateMetadata(): Promise { + try { + const content = await parseMarkdownContent( + "guides/strategy/platform-engineering", + ); + return { + title: + content.frontMatter.title ? + `${content.frontMatter.title} | MooseStack Documentation` + : "Platform Engineering | MooseStack Documentation", + description: + content.frontMatter.description || + "Guide to platform engineering strategy with MooseStack", + }; + } catch (error) { + return { + title: "Platform Engineering | MooseStack Documentation", + description: "Guide to platform engineering strategy with MooseStack", + }; + } +} + +export default async function PlatformEngineeringPage() { + let content; + try { + content = await parseMarkdownContent( + "guides/strategy/platform-engineering", + ); + } catch (error) { + notFound(); + } + + const breadcrumbs = buildDocBreadcrumbs( + "guides/strategy/platform-engineering", + typeof content.frontMatter.title === "string" ? + content.frontMatter.title + : undefined, + ); + + return ( + <> +
+ +
+ {content.isMDX ? + + :
} +
+
+ + + ); +} diff --git a/apps/framework-docs-v2/src/app/templates/layout.tsx b/apps/framework-docs-v2/src/app/templates/layout.tsx new file mode 100644 index 0000000000..25d5d3afb1 --- /dev/null +++ b/apps/framework-docs-v2/src/app/templates/layout.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from "react"; +import { Suspense } from "react"; +import { TemplatesSideNav } from "./templates-side-nav"; +import { AnalyticsProvider } from "@/components/analytics-provider"; +import { SidebarInset } from "@/components/ui/sidebar"; + +interface TemplatesLayoutProps { + children: ReactNode; +} + +export default async function TemplatesLayout({ + children, +}: TemplatesLayoutProps) { + return ( + +
+ }> + + + +
+ {/* Reserve space for the right TOC on xl+ screens */} +
+ {children} +
+
+
+
+
+ ); +} diff --git a/apps/framework-docs-v2/src/app/templates/page.tsx b/apps/framework-docs-v2/src/app/templates/page.tsx new file mode 100644 index 0000000000..ca7f80b21c --- /dev/null +++ b/apps/framework-docs-v2/src/app/templates/page.tsx @@ -0,0 +1,62 @@ +import { notFound } from "next/navigation"; +import type { Metadata } from "next"; +import { parseMarkdownContent } from "@/lib/content"; +import { TOCNav } from "@/components/navigation/toc-nav"; +import { MDXRenderer } from "@/components/mdx-renderer"; +import { DocBreadcrumbs } from "@/components/navigation/doc-breadcrumbs"; +import { buildDocBreadcrumbs } from "@/lib/breadcrumbs"; + +export const dynamic = "force-dynamic"; + +export async function generateMetadata(): Promise { + try { + const content = await parseMarkdownContent("templates/index"); + return { + title: + content.frontMatter.title ? + `${content.frontMatter.title} | MooseStack Documentation` + : "Templates & Apps | MooseStack Documentation", + description: + content.frontMatter.description || + "Browse templates and demo apps for MooseStack", + }; + } catch (error) { + return { + title: "Templates & Apps | MooseStack Documentation", + description: "Browse templates and demo apps for MooseStack", + }; + } +} + +export default async function TemplatesPage() { + let content; + try { + content = await parseMarkdownContent("templates/index"); + } catch (error) { + notFound(); + } + + const breadcrumbs = buildDocBreadcrumbs( + "templates/index", + typeof content.frontMatter.title === "string" ? + content.frontMatter.title + : undefined, + ); + + return ( + <> +
+ +
+ {content.isMDX ? + + :
} +
+
+ + + ); +} diff --git a/apps/framework-docs-v2/src/app/templates/templates-side-nav.tsx b/apps/framework-docs-v2/src/app/templates/templates-side-nav.tsx new file mode 100644 index 0000000000..dc0d894822 --- /dev/null +++ b/apps/framework-docs-v2/src/app/templates/templates-side-nav.tsx @@ -0,0 +1,296 @@ +"use client"; + +import * as React from "react"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { IconX } from "@tabler/icons-react"; + +type LanguageFilter = "typescript" | "python" | null; +type CategoryFilter = ("starter" | "framework" | "example")[]; +type TypeFilter = "template" | "app" | null; + +export function TemplatesSideNav() { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + // Get filter values from URL params + const typeFilter = (searchParams.get("type") as TypeFilter) || null; + const languageFilter = + (searchParams.get("language") as LanguageFilter) || null; + const categoryFilter = React.useMemo(() => { + const categoryParam = searchParams.get("category"); + if (!categoryParam) return []; + return categoryParam + .split(",") + .filter( + (c): c is "starter" | "framework" | "example" => + c === "starter" || c === "framework" || c === "example", + ); + }, [searchParams]); + + const hasActiveFilters = + typeFilter !== null || languageFilter !== null || categoryFilter.length > 0; + + // Update URL params when filters change + const updateFilters = React.useCallback( + (updates: { + type?: TypeFilter; + language?: LanguageFilter; + category?: CategoryFilter; + }) => { + const params = new URLSearchParams(searchParams.toString()); + + if (updates.type !== undefined) { + if (updates.type === null) { + params.delete("type"); + } else { + params.set("type", updates.type); + } + } + + if (updates.language !== undefined) { + if (updates.language === null) { + params.delete("language"); + } else { + params.set("language", updates.language); + } + } + + if (updates.category !== undefined) { + if (updates.category.length === 0) { + params.delete("category"); + } else { + params.set("category", updates.category.join(",")); + } + } + + router.push(`${pathname}?${params.toString()}`); + }, + [router, pathname, searchParams], + ); + + const clearFilters = () => { + updateFilters({ type: null, language: null, category: [] }); + }; + + return ( + + + + Filters + + {/* Type Filter */} + +
+ +
+
+ { + if (checked) { + updateFilters({ type: "template" }); + } else { + updateFilters({ type: null }); + } + }} + /> + +
+
+ { + if (checked) { + updateFilters({ type: "app" }); + } else { + updateFilters({ type: null }); + } + }} + /> + +
+
+
+
+ + {/* Language Filter */} + +
+ +
+
+ { + if (checked) { + updateFilters({ language: "typescript" }); + } else { + updateFilters({ language: null }); + } + }} + /> + +
+
+ { + if (checked) { + updateFilters({ language: "python" }); + } else { + updateFilters({ language: null }); + } + }} + /> + +
+
+
+
+ + {/* Category Filter */} + +
+ +
+
+ { + if (checked) { + updateFilters({ + category: [...categoryFilter, "starter"], + }); + } else { + updateFilters({ + category: categoryFilter.filter( + (c) => c !== "starter", + ), + }); + } + }} + /> + +
+
+ { + if (checked) { + updateFilters({ + category: [...categoryFilter, "framework"], + }); + } else { + updateFilters({ + category: categoryFilter.filter( + (c) => c !== "framework", + ), + }); + } + }} + /> + +
+
+ { + if (checked) { + updateFilters({ + category: [...categoryFilter, "example"], + }); + } else { + updateFilters({ + category: categoryFilter.filter( + (c) => c !== "example", + ), + }); + } + }} + /> + +
+
+
+
+ + {/* Clear Filters Button */} + {hasActiveFilters && ( + + + + Clear Filters + + + )} +
+
+
+
+ ); +} diff --git a/apps/framework-docs-v2/src/components/mdx-renderer.tsx b/apps/framework-docs-v2/src/components/mdx-renderer.tsx index cb30fc5550..3f4ff5ccf7 100644 --- a/apps/framework-docs-v2/src/components/mdx-renderer.tsx +++ b/apps/framework-docs-v2/src/components/mdx-renderer.tsx @@ -27,6 +27,7 @@ import { Security, BreakingChanges, TemplatesGridServer, + CommandSnippet, } from "@/components/mdx"; import { FileTreeFolder, FileTreeFile } from "@/components/mdx/file-tree"; import { CodeEditor } from "@/components/ui/shadcn-io/code-editor"; @@ -120,6 +121,7 @@ export async function MDXRenderer({ source }: MDXRendererProps) { Security, BreakingChanges, TemplatesGridServer, + CommandSnippet, CodeEditor, Separator, Tabs, diff --git a/apps/framework-docs-v2/src/components/mdx/command-snippet.tsx b/apps/framework-docs-v2/src/components/mdx/command-snippet.tsx new file mode 100644 index 0000000000..b43cbaf35b --- /dev/null +++ b/apps/framework-docs-v2/src/components/mdx/command-snippet.tsx @@ -0,0 +1,42 @@ +"use client"; + +import * as React from "react"; +import { + Snippet, + SnippetHeader, + SnippetTabsList, + SnippetTabsTrigger, + SnippetTabsContent, + SnippetCopyButton, +} from "@/components/ui/snippet"; + +interface CommandSnippetProps { + initCommand?: string; + listCommand?: string; + initLabel?: string; + listLabel?: string; +} + +export function CommandSnippet({ + initCommand = "moose init PROJECT_NAME TEMPLATE_NAME", + listCommand = "moose template list", + initLabel = "Init", + listLabel = "List", +}: CommandSnippetProps) { + const [value, setValue] = React.useState("init"); + const currentCommand = value === "init" ? initCommand : listCommand; + + return ( + + + + {initLabel} + {listLabel} + + + + {initCommand} + {listCommand} + + ); +} diff --git a/apps/framework-docs-v2/src/components/mdx/index.ts b/apps/framework-docs-v2/src/components/mdx/index.ts index ebdb8480cf..e34439c4a0 100644 --- a/apps/framework-docs-v2/src/components/mdx/index.ts +++ b/apps/framework-docs-v2/src/components/mdx/index.ts @@ -8,6 +8,7 @@ export { } from "./staggered-card"; export { Callout } from "./callout"; export { LanguageTabs, LanguageTabContent } from "./language-tabs"; +export { CommandSnippet } from "./command-snippet"; export { CodeSnippet } from "./code-snippet"; export { CodeEditorWrapper } from "./code-editor-wrapper"; export { ToggleBlock } from "./toggle-block"; diff --git a/apps/framework-docs-v2/src/components/mdx/template-card.tsx b/apps/framework-docs-v2/src/components/mdx/template-card.tsx index 0e3ccba497..9e78e47774 100644 --- a/apps/framework-docs-v2/src/components/mdx/template-card.tsx +++ b/apps/framework-docs-v2/src/components/mdx/template-card.tsx @@ -11,7 +11,9 @@ import { CardFooter, CardHeader, } from "@/components/ui/card"; -import { IconBrandGithub } from "@tabler/icons-react"; +import { IconBrandGithub, IconRocket } from "@tabler/icons-react"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { Snippet, SnippetCopyButton, @@ -52,12 +54,6 @@ export function TemplateCard({ item, className }: TemplateCardProps) { const template = isTemplate ? (item as TemplateMetadata) : null; const app = !isTemplate ? (item as AppMetadata) : null; - const categoryColors = { - starter: "border-blue-200 dark:border-blue-800", - framework: "border-purple-200 dark:border-purple-800", - example: "border-green-200 dark:border-green-800", - }; - const categoryLabels = { starter: "Starter", framework: "Framework", @@ -82,9 +78,6 @@ export function TemplateCard({ item, className }: TemplateCardProps) { @@ -93,11 +86,8 @@ export function TemplateCard({ item, className }: TemplateCardProps) {
{language && ( - - {language === "typescript" ? "TS" : "Python"} + + {language === "typescript" ? "TypeScript" : "Python"} )} {isTemplate && template && ( @@ -111,70 +101,101 @@ export function TemplateCard({ item, className }: TemplateCardProps) { )}
-

+

{isTemplate ? formatTemplateName(name) : name}

- - {description} - - {frameworks.length > 0 && ( -
-

- Frameworks: -

-
- {frameworks.map((framework) => ( - - {framework} - - ))} -
-
- )} - - {features.length > 0 && ( -
-

- Features: -

-
- {features.map((feature) => ( - - {feature} - - ))} -
+ + {description} + {isTemplate && template && ( +
+
)}
{isTemplate && template && ( -
- -
+ <> + {(frameworks.length > 0 || features.length > 0) && ( + <> + +
+ {frameworks.map((framework) => ( + + {framework} + + ))} + {features.map((feature) => ( + + {feature} + + ))} +
+ + )} + )} - {!isTemplate && app && app.blogPost && ( - - Read Blog Post → - + {!isTemplate && app && ( + <> + {app.blogPost && ( + + Read Blog Post → + + )} + {app.blogPost && (frameworks.length > 0 || features.length > 0) && ( + + )} + {(frameworks.length > 0 || features.length > 0) && ( +
+ {frameworks.map((framework) => ( + + {framework} + + ))} + {features.map((feature) => ( + + {feature} + + ))} +
+ )} + )} - - - View on GitHub - +
+ + +
); diff --git a/apps/framework-docs-v2/src/components/mdx/template-grid.tsx b/apps/framework-docs-v2/src/components/mdx/template-grid.tsx index 753ec43fa1..23eb147392 100644 --- a/apps/framework-docs-v2/src/components/mdx/template-grid.tsx +++ b/apps/framework-docs-v2/src/components/mdx/template-grid.tsx @@ -1,11 +1,11 @@ "use client"; import * as React from "react"; +import { useSearchParams } from "next/navigation"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { TemplateCard } from "./template-card"; import type { ItemMetadata, TemplateMetadata } from "@/lib/template-types"; import { IconSearch, IconX } from "@tabler/icons-react"; @@ -20,13 +20,33 @@ type CategoryFilter = ("starter" | "framework" | "example")[]; type TypeFilter = "template" | "app" | null; export function TemplateGrid({ items, className }: TemplateGridProps) { + const searchParams = useSearchParams(); const [searchQuery, setSearchQuery] = React.useState(""); - const [languageFilter, setLanguageFilter] = - React.useState(null); - const [categoryFilter, setCategoryFilter] = React.useState( - [], - ); - const [typeFilter, setTypeFilter] = React.useState(null); + + // Read filters from URL params (set by TemplatesSideNav) + const typeFilter = React.useMemo(() => { + const type = searchParams.get("type"); + return (type === "template" || type === "app" ? type : null) as TypeFilter; + }, [searchParams]); + + const languageFilter = React.useMemo(() => { + const language = searchParams.get("language"); + return ( + language === "typescript" || language === "python" ? + language + : null) as LanguageFilter; + }, [searchParams]); + + const categoryFilter = React.useMemo(() => { + const categoryParam = searchParams.get("category"); + if (!categoryParam) return []; + return categoryParam + .split(",") + .filter( + (c): c is "starter" | "framework" | "example" => + c === "starter" || c === "framework" || c === "example", + ) as CategoryFilter; + }, [searchParams]); const filteredItems = React.useMemo(() => { return items.filter((item) => { @@ -88,18 +108,10 @@ export function TemplateGrid({ items, className }: TemplateGridProps) { categoryFilter.length > 0 || typeFilter !== null; - const clearFilters = () => { - setSearchQuery(""); - setLanguageFilter(null); - setCategoryFilter([]); - setTypeFilter(null); - }; - return (
- {/* Filters */} -
- {/* Search */} + {/* Search - kept in main content area */} +
)}
- - {/* Type Filter */} -
- - { - if (value === "" || value === undefined) { - setTypeFilter(null); - } else if (value === "template" || value === "app") { - setTypeFilter(value as TypeFilter); - } - }} - variant="outline" - className="w-full" - > - - Templates - - - Apps - - -
- - {/* Language and Category Filters */} -
-
- - { - if (value === "" || value === undefined) { - setLanguageFilter(null); - } else if (value === "typescript" || value === "python") { - setLanguageFilter(value as LanguageFilter); - } - }} - variant="outline" - className="w-full" - > - - TypeScript - - - Python - - -
- -
- - { - setCategoryFilter(value as CategoryFilter); - }} - variant="outline" - className="w-full" - > - - Starter - - - Framework - - - Example - - -
-
- - {/* Clear filters button */} + {/* Results count */} {hasActiveFilters && ( -
- +
{filteredItems.length} item{filteredItems.length !== 1 ? "s" : ""} diff --git a/apps/framework-docs-v2/src/components/ui/checkbox.tsx b/apps/framework-docs-v2/src/components/ui/checkbox.tsx new file mode 100644 index 0000000000..c450e30dd5 --- /dev/null +++ b/apps/framework-docs-v2/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { IconCheck } from "@tabler/icons-react"; + +import { cn } from "@/lib/utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/apps/framework-docs-v2/src/components/ui/command.tsx b/apps/framework-docs-v2/src/components/ui/command.tsx index 525ebddd2f..6be2a79deb 100644 --- a/apps/framework-docs-v2/src/components/ui/command.tsx +++ b/apps/framework-docs-v2/src/components/ui/command.tsx @@ -66,7 +66,7 @@ const CommandInput = React.forwardRef< >( , + VariantProps { + asChild?: boolean; +} + +const Item = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "div"; + return ( + + ); + }, +); +Item.displayName = "Item"; + +const ItemGroup = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemGroup.displayName = "ItemGroup"; + +const ItemSeparator = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemSeparator.displayName = "ItemSeparator"; + +const ItemMedia = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { + variant?: "default" | "icon" | "image"; + } +>(({ className, variant = "default", ...props }, ref) => ( +
+)); +ItemMedia.displayName = "ItemMedia"; + +const ItemContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemContent.displayName = "ItemContent"; + +const ItemTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemTitle.displayName = "ItemTitle"; + +const ItemDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemDescription.displayName = "ItemDescription"; + +const ItemActions = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemActions.displayName = "ItemActions"; + +const ItemHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemHeader.displayName = "ItemHeader"; + +const ItemFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ItemFooter.displayName = "ItemFooter"; + +export { + Item, + ItemGroup, + ItemSeparator, + ItemMedia, + ItemContent, + ItemTitle, + ItemDescription, + ItemActions, + ItemHeader, + ItemFooter, +}; diff --git a/apps/framework-docs-v2/src/components/ui/select.tsx b/apps/framework-docs-v2/src/components/ui/select.tsx index 9b01fc1fb9..0a7d581cd5 100644 --- a/apps/framework-docs-v2/src/components/ui/select.tsx +++ b/apps/framework-docs-v2/src/components/ui/select.tsx @@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-card px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className, )} {...props} diff --git a/apps/framework-docs-v2/src/lib/content.ts b/apps/framework-docs-v2/src/lib/content.ts index 15af3315d9..861e1b7538 100644 --- a/apps/framework-docs-v2/src/lib/content.ts +++ b/apps/framework-docs-v2/src/lib/content.ts @@ -31,7 +31,8 @@ export function getContentFiles(): string[] { /** * Recursively get all markdown files in a directory - * Excludes the 'shared' folder + * Excludes the 'shared' folder and 'templates' folder + * (templates is now an explicit page in the app directory) */ function getAllMarkdownFiles(dir: string, baseDir: string): string[] { const files: string[] = []; @@ -39,11 +40,12 @@ function getAllMarkdownFiles(dir: string, baseDir: string): string[] { for (const entry of entries) { const fullPath = path.join(dir, entry.name); - // Skip the shared folder - if (entry.isDirectory() && entry.name === "shared") { - continue; - } + // Skip the shared folder and templates folder + // (templates is now an explicit page in app directory) if (entry.isDirectory()) { + if (entry.name === "shared" || entry.name === "templates") { + continue; + } files.push(...getAllMarkdownFiles(fullPath, baseDir)); } else if ( entry.isFile() && diff --git a/apps/framework-docs-v2/src/styles/globals.css b/apps/framework-docs-v2/src/styles/globals.css index bcb1670268..2b86243865 100644 --- a/apps/framework-docs-v2/src/styles/globals.css +++ b/apps/framework-docs-v2/src/styles/globals.css @@ -40,9 +40,9 @@ } .dark { - --background: 0 0% 3.9%; + --background: 0 0% 0%; --foreground: 0 0% 98%; - --card: 0 0% 3.9%; + --card: 240 8.9% 3.9%; --card-foreground: 0 0% 98%; --popover: 0 0% 3.9%; --popover-foreground: 0 0% 98%; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20d90858fb..96a5522d1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,7 +62,7 @@ importers: devDependencies: '@clickhouse/client': specifier: latest - version: 1.12.1 + version: 1.13.0 '@iarna/toml': specifier: ^3.0.0 version: 3.0.0 @@ -256,6 +256,9 @@ importers: '@radix-ui/react-avatar': specifier: ^1.0.4 version: 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-collapsible': specifier: ^1.1.11 version: 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -271,6 +274,9 @@ importers: '@radix-ui/react-navigation-menu': specifier: ^1.2.13 version: 1.2.14(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-scroll-area': specifier: ^1.2.2 version: 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -1280,8 +1286,8 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@clickhouse/client-common@1.12.1': - resolution: {integrity: sha512-ccw1N6hB4+MyaAHIaWBwGZ6O2GgMlO99FlMj0B0UEGfjxM9v5dYVYql6FpP19rMwrVAroYs/IgX2vyZEBvzQLg==} + '@clickhouse/client-common@1.13.0': + resolution: {integrity: sha512-QlGUMd3EaKkIRLCv0WW8Rw9cOlqhwQPT+ucNWY8eC4UALsMhJLpa0H7Cd7MYc9CEtTv/xlr3IcYw5Tdho4Hr2g==} '@clickhouse/client-common@1.5.0': resolution: {integrity: sha512-U3vDp+PDnNVEv6kia+Mq5ygnlMZzsYU+3TX+0da3XvL926jzYLMBlIvFUxe2+/5k47ySvnINRC/2QxVK7PC2/A==} @@ -1292,8 +1298,8 @@ packages: '@clickhouse/client-web@1.5.0': resolution: {integrity: sha512-21+c2UJ4cx9SPiIWQThCLULb8h/zng0pNrtTwbbnaoCqMbasyRCyRTHs3wRr7fqRUcZ3p9krIPuN0gnJw3GJ6Q==} - '@clickhouse/client@1.12.1': - resolution: {integrity: sha512-7ORY85rphRazqHzImNXMrh4vsaPrpetFoTWpZYueCO2bbO6PXYDXp/GQ4DgxnGIqbWB/Di1Ai+Xuwq2o7DJ36A==} + '@clickhouse/client@1.13.0': + resolution: {integrity: sha512-uK+zqPaJnAoq3QIOvUNbHtbWUhyg2A/aSbdJtrY2+kawp4SMBLcfIbB9ucRv5Yht1CAa3b24CiUlypkmgarukg==} engines: {node: '>=16'} '@clickhouse/client@1.8.1': @@ -2493,6 +2499,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collapsible@1.1.12': resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} peerDependencies: @@ -2682,6 +2701,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -10104,7 +10136,7 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@clickhouse/client-common@1.12.1': {} + '@clickhouse/client-common@1.13.0': {} '@clickhouse/client-common@1.5.0': {} @@ -10114,9 +10146,9 @@ snapshots: dependencies: '@clickhouse/client-common': 1.5.0 - '@clickhouse/client@1.12.1': + '@clickhouse/client@1.13.0': dependencies: - '@clickhouse/client-common': 1.12.1 + '@clickhouse/client-common': 1.13.0 '@clickhouse/client@1.8.1': dependencies: @@ -11309,6 +11341,22 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -11704,6 +11752,29 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -15267,7 +15338,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -15327,7 +15398,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -15368,7 +15439,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 From 7a3aac7942fa2bddcfb3d9aa56e97bee3787d03a Mon Sep 17 00:00:00 2001 From: Tim Delisle Date: Fri, 14 Nov 2025 21:47:46 -0800 Subject: [PATCH 02/10] attempt build fix --- apps/framework-docs-v2/.npmrc | 20 -------------------- apps/framework-docs-v2/tsconfig.json | 7 +++++-- apps/framework-docs/tsconfig.json | 7 +++++-- packages/ts-config/nextjs.json | 13 ++++--------- 4 files changed, 14 insertions(+), 33 deletions(-) delete mode 100644 apps/framework-docs-v2/.npmrc diff --git a/apps/framework-docs-v2/.npmrc b/apps/framework-docs-v2/.npmrc deleted file mode 100644 index be313dafb4..0000000000 --- a/apps/framework-docs-v2/.npmrc +++ /dev/null @@ -1,20 +0,0 @@ -# Force all dependencies to be hoisted locally to this app's node_modules -# This prevents TypeScript from finding React types in nested node_modules -# This works with the root .npmrc which prevents React from being hoisted to root -# Since this app only uses React 19, we can safely hoist everything locally here -# -# IMPORTANT: We use hoist-pattern instead of shamefully-hoist=true to avoid -# conflicts with the root hoisting pattern when running pnpm add from this directory -# (e.g., via shadcn CLI). This hoists everything locally without modifying root structure. -hoist-pattern[]=* - -# Match root public-hoist-pattern to prevent ERR_PNPM_PUBLIC_HOIST_PATTERN_DIFF -# This ensures compatibility when running pnpm add from this directory -public-hoist-pattern[]=!react -public-hoist-pattern[]=!react-dom -public-hoist-pattern[]=!react/jsx-runtime -public-hoist-pattern[]=!react-dom/server -public-hoist-pattern[]=!react/jsx-dev-runtime -public-hoist-pattern[]=!@types/react -public-hoist-pattern[]=!@types/react-dom - diff --git a/apps/framework-docs-v2/tsconfig.json b/apps/framework-docs-v2/tsconfig.json index 7a7e4d75f0..29c0f8e1cc 100644 --- a/apps/framework-docs-v2/tsconfig.json +++ b/apps/framework-docs-v2/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@repo/ts-config/base.json", + "extends": "@repo/ts-config/nextjs.json", "compilerOptions": { "plugins": [ { @@ -21,7 +21,10 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", - "incremental": true + "incremental": true, + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] diff --git a/apps/framework-docs/tsconfig.json b/apps/framework-docs/tsconfig.json index 9b213fb0d0..aa438ff45e 100644 --- a/apps/framework-docs/tsconfig.json +++ b/apps/framework-docs/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@repo/ts-config/base.json", + "extends": "@repo/ts-config/nextjs.json", "compilerOptions": { "plugins": [ { @@ -21,7 +21,10 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false }, "include": [ "next-env.d.ts", diff --git a/packages/ts-config/nextjs.json b/packages/ts-config/nextjs.json index 6a8050d396..f83274c7ec 100644 --- a/packages/ts-config/nextjs.json +++ b/packages/ts-config/nextjs.json @@ -1,15 +1,10 @@ { "$schema": "https://json.schemastore.org/tsconfig", - "display": "Next.js", + "display": "Next.js App", "extends": "./base.json", "compilerOptions": { - "plugins": [{ "name": "next" }], - "moduleResolution": "NodeNext", - "allowJs": true, - "jsx": "preserve", - "noEmit": true, - "paths": { - "@ui/*": ["../../packages/design-system/*"] - } + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false } } From 5a3dceca39517da5587f238f621e8a507addaf78 Mon Sep 17 00:00:00 2001 From: Tim Delisle Date: Sat, 15 Nov 2025 16:05:45 -0800 Subject: [PATCH 03/10] updated template cards --- .../src/components/mdx/template-card.tsx | 156 +++++++++--------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/apps/framework-docs-v2/src/components/mdx/template-card.tsx b/apps/framework-docs-v2/src/components/mdx/template-card.tsx index 9e78e47774..81225fb132 100644 --- a/apps/framework-docs-v2/src/components/mdx/template-card.tsx +++ b/apps/framework-docs-v2/src/components/mdx/template-card.tsx @@ -11,9 +11,8 @@ import { CardFooter, CardHeader, } from "@/components/ui/card"; -import { IconBrandGithub, IconRocket } from "@tabler/icons-react"; +import { IconBrandGithub, IconRocket, IconBook } from "@tabler/icons-react"; import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; import { Snippet, SnippetCopyButton, @@ -53,6 +52,7 @@ export function TemplateCard({ item, className }: TemplateCardProps) { const isTemplate = item.type === "template"; const template = isTemplate ? (item as TemplateMetadata) : null; const app = !isTemplate ? (item as AppMetadata) : null; + const [chipsExpanded, setChipsExpanded] = React.useState(false); const categoryLabels = { starter: "Starter", @@ -74,6 +74,17 @@ export function TemplateCard({ item, className }: TemplateCardProps) { const description = isTemplate ? template!.description : app!.description; const name = isTemplate ? template!.name : app!.name; + // Combine frameworks and features into a single array with type info + const allChips = [ + ...frameworks.map((f) => ({ value: f, type: "framework" as const })), + ...features.map((f) => ({ value: f, type: "feature" as const })), + ]; + + const MAX_VISIBLE_CHIPS = 3; + const visibleChips = + chipsExpanded ? allChips : allChips.slice(0, MAX_VISIBLE_CHIPS); + const hiddenCount = allChips.length - MAX_VISIBLE_CHIPS; + return (
-
- {language && ( - - {language === "typescript" ? "TypeScript" : "Python"} - - )} - {isTemplate && template && ( - - {categoryLabels[template.category]} - - )} - {!isTemplate && ( - - Demo App - - )} +
+ {(() => { + const labels: string[] = []; + if (language) { + labels.push( + language === "typescript" ? "TypeScript" : "Python", + ); + } + if (isTemplate && template) { + labels.push(categoryLabels[template.category]); + } + if (!isTemplate) { + labels.push("Demo App"); + } + return ( + + {labels.join(" • ")} + + ); + })()}

{isTemplate ? formatTemplateName(name) : name}

+ {allChips.length > 0 && ( +
+ {visibleChips.map((chip) => ( + + {chip.value} + + ))} + {!chipsExpanded && hiddenCount > 0 && ( + setChipsExpanded(true)} + > + {hiddenCount} more + + )} + {chipsExpanded && ( + setChipsExpanded(false)} + > + Show less + + )} +
+ )}
@@ -116,66 +164,6 @@ export function TemplateCard({ item, className }: TemplateCardProps) { )} - {isTemplate && template && ( - <> - {(frameworks.length > 0 || features.length > 0) && ( - <> - -
- {frameworks.map((framework) => ( - - {framework} - - ))} - {features.map((feature) => ( - - {feature} - - ))} -
- - )} - - )} - {!isTemplate && app && ( - <> - {app.blogPost && ( - - Read Blog Post → - - )} - {app.blogPost && (frameworks.length > 0 || features.length > 0) && ( - - )} - {(frameworks.length > 0 || features.length > 0) && ( -
- {frameworks.map((framework) => ( - - {framework} - - ))} - {features.map((feature) => ( - - {feature} - - ))} -
- )} - - )}
+ {!isTemplate && app && app.blogPost && ( + + )} + +
+ ); +} diff --git a/apps/framework-docs-v2/src/components/guides/guide-steps-nav.tsx b/apps/framework-docs-v2/src/components/guides/guide-steps-nav.tsx new file mode 100644 index 0000000000..c21d443733 --- /dev/null +++ b/apps/framework-docs-v2/src/components/guides/guide-steps-nav.tsx @@ -0,0 +1,190 @@ +"use client"; + +import * as React from "react"; +import { usePathname, useSearchParams } from "next/navigation"; +import Link from "next/link"; +import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { useLanguage } from "@/hooks/use-language"; + +interface Step { + slug: string; + stepNumber: number; + title: string; +} + +interface GuideStepsNavProps { + steps: Step[]; + currentSlug: string; + children?: React.ReactNode; +} + +export function GuideStepsNav({ + steps, + currentSlug, + children, +}: GuideStepsNavProps) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { language } = useLanguage(); + const [currentStepIndex, setCurrentStepIndex] = React.useState(0); + + // Determine current step from URL hash or default to first step + React.useEffect(() => { + const hash = window.location.hash; + if (hash) { + const stepMatch = hash.match(/step-(\d+)/); + if (stepMatch) { + const stepNum = parseInt(stepMatch[1]!, 10); + const index = steps.findIndex((s) => s.stepNumber === stepNum); + if (index >= 0) { + setCurrentStepIndex(index); + } + } + } + }, [steps]); + + // Update URL hash and show/hide steps when step changes + React.useEffect(() => { + if (steps.length > 0 && currentStepIndex < steps.length) { + const currentStep = steps[currentStepIndex]; + if (currentStep) { + const hasPrevious = currentStepIndex > 0; + const hasNext = currentStepIndex < steps.length - 1; + + // Update URL hash + window.history.replaceState( + null, + "", + `${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}#step-${currentStep.stepNumber}`, + ); + + // Show/hide step content + const stepContents = document.querySelectorAll(".step-content"); + stepContents.forEach((content, index) => { + if (index === currentStepIndex) { + content.classList.remove("hidden"); + content.classList.add("block"); + } else { + content.classList.add("hidden"); + content.classList.remove("block"); + } + }); + + // Update card header with current step info + const cardTitle = document.querySelector(".step-card-title"); + const cardBadge = document.querySelector(".step-card-badge"); + const buttonsContainer = document.getElementById( + "step-nav-buttons-container", + ); + if (cardTitle) cardTitle.textContent = currentStep.title; + if (cardBadge) + cardBadge.textContent = currentStep.stepNumber.toString(); + + // Update navigation buttons + if (buttonsContainer) { + buttonsContainer.innerHTML = ` + + + `; + } + } + } + }, [currentStepIndex, steps, pathname, searchParams]); + + if (steps.length === 0) return null; + + const currentStep = steps[currentStepIndex]; + const hasPrevious = currentStepIndex > 0; + const hasNext = currentStepIndex < steps.length - 1; + + const goToStep = (index: number) => { + if (index >= 0 && index < steps.length) { + setCurrentStepIndex(index); + // Scroll to top of steps section + const element = document.getElementById("guide-steps"); + if (element) { + element.scrollIntoView({ behavior: "smooth", block: "start" }); + } + } + }; + + // Expose goToStep to window for button onclick handlers + React.useEffect(() => { + (window as any).__goToStep = goToStep; + return () => { + delete (window as any).__goToStep; + }; + }, [goToStep]); + + const buildUrl = (stepSlug: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set("lang", language); + return `/${stepSlug}?${params.toString()}`; + }; + + return ( + <> +
+

Implementation Steps

+
+ {steps.map((step, index) => ( + + ))} +
+
+ + {children} + + {/* Step list for navigation */} +
+

All Steps

+
+ {steps.map((step, index) => ( + { + e.preventDefault(); + goToStep(index); + }} + className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors ${ + index === currentStepIndex ? + "bg-accent text-accent-foreground" + : "hover:bg-accent/50" + }`} + > + + {step.stepNumber} + + {step.title} + + ))} +
+
+ + ); +} diff --git a/apps/framework-docs-v2/src/components/guides/guide-steps-wrapper.tsx b/apps/framework-docs-v2/src/components/guides/guide-steps-wrapper.tsx new file mode 100644 index 0000000000..f23846b21b --- /dev/null +++ b/apps/framework-docs-v2/src/components/guides/guide-steps-wrapper.tsx @@ -0,0 +1,66 @@ +import { GuideStepsNav } from "./guide-steps-nav"; +import { StepContent } from "./step-content"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; + +interface GuideStepsWrapperProps { + steps: Array<{ + slug: string; + stepNumber: number; + title: string; + }>; + stepsWithContent: Array<{ + slug: string; + stepNumber: number; + title: string; + content: string | null; + isMDX: boolean; + }>; + currentSlug: string; +} + +export async function GuideStepsWrapper({ + steps, + stepsWithContent, + currentSlug, +}: GuideStepsWrapperProps) { + // Render all step content on the server + const renderedSteps = await Promise.all( + stepsWithContent.map(async (step, index) => { + if (!step.content) return null; + return ( +
+ +
+ ); + }), + ); + + return ( +
+ + + +
+
+ + {steps[0]?.stepNumber || 1} + + + {steps[0]?.title || "Step 1"} + +
+
+
+
+ +
{renderedSteps}
+
+
+
+ ); +} diff --git a/apps/framework-docs-v2/src/components/guides/guide-steps.tsx b/apps/framework-docs-v2/src/components/guides/guide-steps.tsx new file mode 100644 index 0000000000..ab5f980354 --- /dev/null +++ b/apps/framework-docs-v2/src/components/guides/guide-steps.tsx @@ -0,0 +1,183 @@ +"use client"; + +import * as React from "react"; +import { usePathname, useSearchParams } from "next/navigation"; +import Link from "next/link"; +import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useLanguage } from "@/hooks/use-language"; + +interface Step { + slug: string; + stepNumber: number; + title: string; +} + +interface GuideStepsProps { + steps: Step[]; + renderedSteps: React.ReactElement[]; + currentSlug: string; +} + +export function GuideSteps({ + steps, + renderedSteps, + currentSlug, +}: GuideStepsProps) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { language } = useLanguage(); + const [currentStepIndex, setCurrentStepIndex] = React.useState(0); + + // Determine current step from URL hash or default to first step + React.useEffect(() => { + const hash = window.location.hash; + if (hash) { + const stepMatch = hash.match(/step-(\d+)/); + if (stepMatch) { + const stepNum = parseInt(stepMatch[1]!, 10); + const index = steps.findIndex((s) => s.stepNumber === stepNum); + if (index >= 0) { + setCurrentStepIndex(index); + } + } + } + }, [steps]); + + // Update URL hash when step changes + React.useEffect(() => { + if (steps.length > 0 && currentStepIndex < steps.length) { + const currentStep = steps[currentStepIndex]; + if (currentStep) { + window.history.replaceState( + null, + "", + `${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}#step-${currentStep.stepNumber}`, + ); + } + } + }, [currentStepIndex, steps, pathname, searchParams]); + + if (steps.length === 0) return null; + + const currentStep = steps[currentStepIndex]; + const currentRenderedStep = renderedSteps[currentStepIndex]; + const hasPrevious = currentStepIndex > 0; + const hasNext = currentStepIndex < steps.length - 1; + + const goToStep = (index: number) => { + if (index >= 0 && index < steps.length) { + setCurrentStepIndex(index); + // Scroll to top of steps section + const element = document.getElementById("guide-steps"); + if (element) { + element.scrollIntoView({ behavior: "smooth", block: "start" }); + } + } + }; + + const buildUrl = (stepSlug: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set("lang", language); + return `/${stepSlug}?${params.toString()}`; + }; + + return ( +
+
+

Implementation Steps

+
+ {steps.map((step, index) => ( + + ))} +
+
+ + + +
+
+ {currentStep.stepNumber} + {currentStep.title} +
+
+ + +
+
+
+ +
+ {renderedSteps.map((stepContent, index) => ( +
+ {stepContent || ( +
+ Step content not available +
+ )} +
+ ))} +
+
+
+ + {/* Step list for navigation */} +
+

All Steps

+
+ {steps.map((step, index) => ( + { + e.preventDefault(); + goToStep(index); + }} + className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors ${ + index === currentStepIndex ? + "bg-accent text-accent-foreground" + : "hover:bg-accent/50" + }`} + > + + {step.stepNumber} + + {step.title} + + ))} +
+
+
+ ); +} diff --git a/apps/framework-docs-v2/src/components/guides/step-content.tsx b/apps/framework-docs-v2/src/components/guides/step-content.tsx new file mode 100644 index 0000000000..b64c151cfc --- /dev/null +++ b/apps/framework-docs-v2/src/components/guides/step-content.tsx @@ -0,0 +1,22 @@ +import { MDXRenderer } from "@/components/mdx-renderer"; + +interface StepContentProps { + content: string; + isMDX: boolean; +} + +export async function StepContent({ content, isMDX }: StepContentProps) { + if (!content) { + return ( +
Step content not available
+ ); + } + + return ( +
+ {isMDX ? + + :
} +
+ ); +} diff --git a/apps/framework-docs-v2/src/components/navigation/side-nav.tsx b/apps/framework-docs-v2/src/components/navigation/side-nav.tsx index fb1380ed5f..e3b5ef66bf 100644 --- a/apps/framework-docs-v2/src/components/navigation/side-nav.tsx +++ b/apps/framework-docs-v2/src/components/navigation/side-nav.tsx @@ -9,9 +9,11 @@ import { Sidebar, SidebarContent, SidebarGroup, + SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, + SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, @@ -174,10 +176,21 @@ function NavItemComponent({ item }: { item: NavPage }) { }, [hasChildren, item.children, pathname]); const defaultOpen = isActive || hasActiveDescendant; + const [isOpen, setIsOpen] = React.useState(defaultOpen); + + // Update open state when active state changes + React.useEffect(() => { + setIsOpen(isActive || hasActiveDescendant); + }, [isActive, hasActiveDescendant]); if (hasChildren) { return ( - + @@ -224,6 +237,91 @@ function NavItemComponent({ item }: { item: NavPage }) { ); } +function NestedNavItemComponent({ + item, + pathname, + searchParams, + language, +}: { + item: NavPage; + pathname: string; + searchParams: URLSearchParams; + language: string; +}) { + const childHasChildren = item.children && item.children.length > 0; + const childHref = (() => { + const params = new URLSearchParams(searchParams.toString()); + params.set("lang", language); + return `/${item.slug}?${params.toString()}`; + })(); + const childIsActive = pathname === `/${item.slug}`; + + // Recursively check if any descendant is active + const checkDescendant = (children: NavItem[]): boolean => { + return children.some((c) => { + if (c.type === "page") { + if (pathname === `/${c.slug}`) return true; + if (c.children) return checkDescendant(c.children); + } + return false; + }); + }; + const hasActiveDescendant = + childHasChildren ? checkDescendant(item.children!) : false; + const defaultOpen = childIsActive || hasActiveDescendant; + const [isOpen, setIsOpen] = React.useState(defaultOpen); + + React.useEffect(() => { + setIsOpen(childIsActive || hasActiveDescendant); + }, [childIsActive, hasActiveDescendant]); + + if (childHasChildren) { + return ( + + + + + {item.icon && } + {item.title} + + + + + + Toggle + + + + + {renderNavChildren( + item.children!, + pathname, + searchParams, + language, + )} + + + + + ); + } + + return ( + + + + {item.icon && } + {item.title} + + + + ); +} + function renderNavChildren( children: NavItem[], pathname: string, @@ -231,169 +329,19 @@ function renderNavChildren( language: string, ): React.ReactNode[] { const elements: React.ReactNode[] = []; - let currentGroup: NavPage[] = []; - let currentLabel: string | null = null; - - const flushGroup = () => { - if (currentGroup.length > 0) { - currentGroup.forEach((child: NavPage) => { - const childHasChildren = child.children && child.children.length > 0; - const childHref = (() => { - const params = new URLSearchParams(searchParams.toString()); - params.set("lang", language); - return `/${child.slug}?${params.toString()}`; - })(); - const childIsActive = pathname === `/${child.slug}`; - - // Recursively check if any descendant is active - const checkDescendant = (children: NavItem[]): boolean => { - return children.some((c) => { - if (c.type === "page") { - if (pathname === `/${c.slug}`) return true; - if (c.children) return checkDescendant(c.children); - } - return false; - }); - }; - const hasActiveDescendant = - childHasChildren ? checkDescendant(child.children!) : false; - const defaultOpen = childIsActive || hasActiveDescendant; - - if (childHasChildren) { - // Render nested collapsible item - using same pattern as top-level - // SidebarMenuSubItem needs relative positioning for SidebarMenuAction - elements.push( - - - - - {child.icon && } - {child.title} - - - - - - Toggle - - - - - {renderNavChildren( - child.children!, - pathname, - searchParams, - language, - )} - - - - , - ); - } else { - // Render simple link for leaf nodes - elements.push( - - - - {child.icon && } - {child.title} - - - , - ); - } - }); - currentGroup = []; - } - }; children.forEach((child) => { - if (child.type === "separator") { - flushGroup(); - currentLabel = null; - } else if (child.type === "label") { - flushGroup(); - currentLabel = child.title; - } else if (child.type === "section") { - flushGroup(); - // Check if any item in the section is active to determine default open state - const hasActiveItem = child.items.some((item) => { - if (item.type === "page") { - return pathname === `/${item.slug}`; - } - return false; - }); - - // Render collapsible section within the submenu - // We need to render the trigger and items as siblings, not nested - const sectionItems: React.ReactNode[] = []; - child.items.forEach((item) => { - if (item.type === "page") { - const itemHref = (() => { - const params = new URLSearchParams(searchParams.toString()); - params.set("lang", language); - return `/${item.slug}?${params.toString()}`; - })(); - const itemIsActive = pathname === `/${item.slug}`; - sectionItems.push( - - - - {item.icon && } - {item.title} - - - , - ); - } - }); - - elements.push( - - - - - - {child.icon && ( - - )} - - {child.title} - - - - - - - - {sectionItems} - - , - ); - } else if (child.type === "page") { - if (currentLabel && currentGroup.length === 0) { - // Add label before first item in group - elements.push( - - {currentLabel} - , - ); - } - currentGroup.push(child); - } + if (child.type !== "page") return; + elements.push( + , + ); }); - flushGroup(); + return elements; } diff --git a/apps/framework-docs-v2/src/config/navigation.ts b/apps/framework-docs-v2/src/config/navigation.ts index b387df8438..e1348ca9e9 100644 --- a/apps/framework-docs-v2/src/config/navigation.ts +++ b/apps/framework-docs-v2/src/config/navigation.ts @@ -1,3 +1,4 @@ +import * as React from "react"; import type { Language } from "@/lib/content-types"; import { IconChartArea, @@ -877,259 +878,85 @@ const guidesNavigationConfig: NavigationConfig = [ items: [ { type: "page", - slug: "guides/applications/performant-dashboards/overview", + slug: "guides/applications/performant-dashboards/guide-overview", title: "Performant Dashboards", icon: IconChartLine, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/applications/performant-dashboards/guide-overview", - title: "Overview", + slug: "guides/applications/performant-dashboards/existing-oltp-db", + title: "From Existing OLTP DB", languages: ["typescript", "python"], }, { - type: "section", - title: "Existing OLTP DB", - items: [ - { - type: "page", - slug: "guides/applications/performant-dashboards/existing-oltp-db/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/performant-dashboards/existing-oltp-db/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/performant-dashboards/existing-oltp-db/1-setup-connection", - title: "Setup Connection", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/performant-dashboards/existing-oltp-db/2-create-materialized-view", - title: "Create Materialized View", - languages: ["typescript", "python"], - }, - ], - }, - { - type: "section", + type: "page", + slug: "guides/applications/performant-dashboards/new-application", title: "New Application", - items: [ - { - type: "page", - slug: "guides/applications/performant-dashboards/new-application/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/performant-dashboards/new-application/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/performant-dashboards/new-application/1-initialize-project", - title: "Initialize Project", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/applications/in-app-chat-analytics/overview", + slug: "guides/applications/in-app-chat-analytics/guide-overview", title: "In-App Chat Analytics", icon: IconMessageChatbot, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/applications/in-app-chat-analytics/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/applications/in-app-chat-analytics/existing-chat-system", title: "Existing Chat System", - items: [ - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/existing-chat-system/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/existing-chat-system/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/existing-chat-system/1-integrate-event-tracking", - title: "Integrate Event Tracking", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/applications/in-app-chat-analytics/new-chat-feature", title: "New Chat Feature", - items: [ - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/new-chat-feature/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/new-chat-feature/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/in-app-chat-analytics/new-chat-feature/1-setup-chat-schema", - title: "Setup Chat Schema", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/applications/automated-reports/overview", + slug: "guides/applications/automated-reports/guide-overview", title: "Automated Reports", icon: IconFileReport, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/applications/automated-reports/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/applications/automated-reports/scheduled-reports", title: "Scheduled Reports", - items: [ - { - type: "page", - slug: "guides/applications/automated-reports/scheduled-reports/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/automated-reports/scheduled-reports/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/automated-reports/scheduled-reports/1-create-report-template", - title: "Create Report Template", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/applications/automated-reports/event-driven-reports", title: "Event-Driven Reports", - items: [ - { - type: "page", - slug: "guides/applications/automated-reports/event-driven-reports/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/automated-reports/event-driven-reports/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/automated-reports/event-driven-reports/1-setup-event-triggers", - title: "Setup Event Triggers", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/applications/going-to-production/overview", + slug: "guides/applications/going-to-production/guide-overview", title: "Going to Production", icon: IconCloudUpload, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/applications/going-to-production/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/applications/going-to-production/local-development", title: "Local Development", - items: [ - { - type: "page", - slug: "guides/applications/going-to-production/local-development/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/going-to-production/local-development/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/going-to-production/local-development/1-prepare-environment", - title: "Prepare Environment", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/applications/going-to-production/staging-environment", title: "Staging Environment", - items: [ - { - type: "page", - slug: "guides/applications/going-to-production/staging-environment/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/going-to-production/staging-environment/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/applications/going-to-production/staging-environment/1-deploy-infrastructure", - title: "Deploy Infrastructure", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, @@ -1141,214 +968,70 @@ const guidesNavigationConfig: NavigationConfig = [ items: [ { type: "page", - slug: "guides/data-management/migrations/overview", + slug: "guides/data-management/migrations/guide-overview", title: "Migrations", icon: IconDatabaseImport, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-management/migrations/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/data-management/migrations/schema-changes", title: "Schema Changes", - items: [ - { - type: "page", - slug: "guides/data-management/migrations/schema-changes/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/schema-changes/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/schema-changes/1-create-migration-script", - title: "Create Migration Script", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/data-management/migrations/data-migration", title: "Data Migration", - items: [ - { - type: "page", - slug: "guides/data-management/migrations/data-migration/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/data-migration/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/data-migration/1-backup-existing-data", - title: "Backup Existing Data", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/data-management/migrations/version-upgrades", title: "Version Upgrades", - items: [ - { - type: "page", - slug: "guides/data-management/migrations/version-upgrades/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/version-upgrades/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/migrations/version-upgrades/1-review-changelog", - title: "Review Changelog", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/data-management/impact-analysis/overview", + slug: "guides/data-management/impact-analysis/guide-overview", title: "Impact Analysis", icon: IconChartDots, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-management/impact-analysis/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/data-management/impact-analysis/schema-changes", title: "Schema Changes", - items: [ - { - type: "page", - slug: "guides/data-management/impact-analysis/schema-changes/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/impact-analysis/schema-changes/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/impact-analysis/schema-changes/1-identify-dependencies", - title: "Identify Dependencies", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/data-management/impact-analysis/query-changes", title: "Query Changes", - items: [ - { - type: "page", - slug: "guides/data-management/impact-analysis/query-changes/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/impact-analysis/query-changes/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/impact-analysis/query-changes/1-analyze-query-performance", - title: "Analyze Query Performance", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/data-management/change-data-capture/overview", + slug: "guides/data-management/change-data-capture/guide-overview", title: "Change Data Capture", icon: IconBolt, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-management/change-data-capture/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/data-management/change-data-capture/database-cdc", title: "Database CDC", - items: [ - { - type: "page", - slug: "guides/data-management/change-data-capture/database-cdc/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/change-data-capture/database-cdc/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/change-data-capture/database-cdc/1-enable-cdc-logging", - title: "Enable CDC Logging", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/data-management/change-data-capture/application-events", title: "Application Events", - items: [ - { - type: "page", - slug: "guides/data-management/change-data-capture/application-events/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/change-data-capture/application-events/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-management/change-data-capture/application-events/1-implement-event-emitter", - title: "Implement Event Emitter", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, @@ -1360,280 +1043,112 @@ const guidesNavigationConfig: NavigationConfig = [ items: [ { type: "page", - slug: "guides/data-warehousing/customer-data-platform/overview", + slug: "guides/data-warehousing/customer-data-platform/guide-overview", title: "Customer Data Platform", icon: IconUsers, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-warehousing/customer-data-platform/guide-overview", - title: "Overview", - languages: ["typescript", "python"], - }, - { - type: "section", + slug: "guides/data-warehousing/customer-data-platform/existing-customer-data", title: "Existing Customer Data", - items: [ - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/existing-customer-data/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/existing-customer-data/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/existing-customer-data/1-consolidate-data-sources", - title: "Consolidate Data Sources", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, { - type: "section", + type: "page", + slug: "guides/data-warehousing/customer-data-platform/multi-source-integration", title: "Multi-Source Integration", - items: [ - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/multi-source-integration/overview", - title: "Implementation Overview", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/multi-source-integration/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/customer-data-platform/multi-source-integration/1-setup-connectors", - title: "Setup Connectors", - languages: ["typescript", "python"], - }, - ], + languages: ["typescript", "python"], }, ], }, { type: "page", - slug: "guides/data-warehousing/operational-analytics/overview", + slug: "guides/data-warehousing/operational-analytics/guide-overview", title: "Operational Analytics", icon: IconChartBarOff, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-warehousing/operational-analytics/application-metrics/overview", + slug: "guides/data-warehousing/operational-analytics/application-metrics", title: "Application Metrics", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/operational-analytics/application-metrics/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/operational-analytics/application-metrics/1-instrument-application", - title: "Instrument Application", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/data-warehousing/operational-analytics/infrastructure-monitoring/overview", + slug: "guides/data-warehousing/operational-analytics/infrastructure-monitoring", title: "Infrastructure Monitoring", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/operational-analytics/infrastructure-monitoring/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/operational-analytics/infrastructure-monitoring/1-collect-system-metrics", - title: "Collect System Metrics", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/data-warehousing/startup-metrics/overview", + slug: "guides/data-warehousing/startup-metrics/guide-overview", title: "Startup Metrics", icon: IconChartBar, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-warehousing/startup-metrics/product-metrics/overview", + slug: "guides/data-warehousing/startup-metrics/product-metrics", title: "Product Metrics", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/startup-metrics/product-metrics/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/startup-metrics/product-metrics/1-define-kpis", - title: "Define KPIs", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/data-warehousing/startup-metrics/business-metrics/overview", + slug: "guides/data-warehousing/startup-metrics/business-metrics", title: "Business Metrics", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/startup-metrics/business-metrics/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/startup-metrics/business-metrics/1-setup-revenue-tracking", - title: "Setup Revenue Tracking", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/data-warehousing/connectors/overview", + slug: "guides/data-warehousing/connectors/guide-overview", title: "Connectors", icon: IconStack, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-warehousing/connectors/database-connector/overview", + slug: "guides/data-warehousing/connectors/database-connector", title: "Database Connector", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/connectors/database-connector/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/connectors/database-connector/1-configure-connection", - title: "Configure Connection", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/data-warehousing/connectors/api-connector/overview", + slug: "guides/data-warehousing/connectors/api-connector", title: "API Connector", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/connectors/api-connector/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/connectors/api-connector/1-setup-authentication", - title: "Setup Authentication", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/data-warehousing/connectors/custom-connector/overview", + slug: "guides/data-warehousing/connectors/custom-connector", title: "Custom Connector", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/connectors/custom-connector/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/connectors/custom-connector/1-create-connector-class", - title: "Create Connector Class", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/data-warehousing/pipelines/overview", + slug: "guides/data-warehousing/pipelines/guide-overview", title: "Pipelines", icon: IconRoute, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/data-warehousing/pipelines/etl-pipeline/overview", + slug: "guides/data-warehousing/pipelines/etl-pipeline", title: "ETL Pipeline", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/pipelines/etl-pipeline/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/pipelines/etl-pipeline/1-extract-data", - title: "Extract Data", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/data-warehousing/pipelines/streaming-pipeline/overview", + slug: "guides/data-warehousing/pipelines/streaming-pipeline", title: "Streaming Pipeline", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/data-warehousing/pipelines/streaming-pipeline/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/data-warehousing/pipelines/streaming-pipeline/1-setup-stream-source", - title: "Setup Stream Source", - languages: ["typescript", "python"], - }, - ], }, ], }, @@ -1645,99 +1160,43 @@ const guidesNavigationConfig: NavigationConfig = [ items: [ { type: "page", - slug: "guides/methodology/data-as-code/overview", + slug: "guides/methodology/data-as-code/guide-overview", title: "Data as Code", icon: IconCode, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/methodology/data-as-code/version-control-setup/overview", + slug: "guides/methodology/data-as-code/version-control-setup", title: "Version Control Setup", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/methodology/data-as-code/version-control-setup/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/methodology/data-as-code/version-control-setup/1-initialize-repository", - title: "Initialize Repository", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/methodology/data-as-code/cicd-integration/overview", + slug: "guides/methodology/data-as-code/cicd-integration", title: "CI/CD Integration", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/methodology/data-as-code/cicd-integration/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/methodology/data-as-code/cicd-integration/1-create-pipeline-config", - title: "Create Pipeline Config", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/methodology/dora-for-data/overview", + slug: "guides/methodology/dora-for-data/guide-overview", title: "DORA for Data", icon: IconTrendingUp, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/methodology/dora-for-data/deployment-frequency/overview", + slug: "guides/methodology/dora-for-data/deployment-frequency", title: "Deployment Frequency", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/methodology/dora-for-data/deployment-frequency/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/methodology/dora-for-data/deployment-frequency/1-measure-current-frequency", - title: "Measure Current Frequency", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/methodology/dora-for-data/lead-time/overview", + slug: "guides/methodology/dora-for-data/lead-time", title: "Lead Time", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/methodology/dora-for-data/lead-time/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/methodology/dora-for-data/lead-time/1-track-change-lifecycle", - title: "Track Change Lifecycle", - languages: ["typescript", "python"], - }, - ], }, ], }, @@ -1749,197 +1208,85 @@ const guidesNavigationConfig: NavigationConfig = [ items: [ { type: "page", - slug: "guides/strategy/ai-enablement/overview", + slug: "guides/strategy/ai-enablement/guide-overview", title: "AI Enablement", icon: IconBrain, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/strategy/ai-enablement/llm-integration/overview", + slug: "guides/strategy/ai-enablement/llm-integration", title: "LLM Integration", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/ai-enablement/llm-integration/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/ai-enablement/llm-integration/1-choose-llm-provider", - title: "Choose LLM Provider", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/strategy/ai-enablement/vector-search/overview", + slug: "guides/strategy/ai-enablement/vector-search", title: "Vector Search", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/ai-enablement/vector-search/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/ai-enablement/vector-search/1-setup-vector-database", - title: "Setup Vector Database", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/strategy/data-foundation/overview", + slug: "guides/strategy/data-foundation/guide-overview", title: "Data Foundation", icon: IconDatabase, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/strategy/data-foundation/greenfield-project/overview", + slug: "guides/strategy/data-foundation/greenfield-project", title: "Greenfield Project", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/data-foundation/greenfield-project/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/data-foundation/greenfield-project/1-design-data-architecture", - title: "Design Data Architecture", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/strategy/data-foundation/legacy-system-migration/overview", + slug: "guides/strategy/data-foundation/legacy-system-migration", title: "Legacy System Migration", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/data-foundation/legacy-system-migration/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/data-foundation/legacy-system-migration/1-assess-current-state", - title: "Assess Current State", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/strategy/platform-engineering/overview", + slug: "guides/strategy/platform-engineering/guide-overview", title: "Platform Engineering", icon: IconServer, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/strategy/platform-engineering/internal-platform/overview", + slug: "guides/strategy/platform-engineering/internal-platform", title: "Internal Platform", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/platform-engineering/internal-platform/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/platform-engineering/internal-platform/1-define-platform-scope", - title: "Define Platform Scope", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/strategy/platform-engineering/self-service-tools/overview", + slug: "guides/strategy/platform-engineering/self-service-tools", title: "Self-Service Tools", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/platform-engineering/self-service-tools/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/platform-engineering/self-service-tools/1-create-developer-portal", - title: "Create Developer Portal", - languages: ["typescript", "python"], - }, - ], }, ], }, { type: "page", - slug: "guides/strategy/olap-evaluation/overview", + slug: "guides/strategy/olap-evaluation/guide-overview", title: "OLAP Evaluation", icon: IconDatabase, languages: ["typescript", "python"], children: [ { type: "page", - slug: "guides/strategy/olap-evaluation/performance-requirements/overview", + slug: "guides/strategy/olap-evaluation/performance-requirements", title: "Performance Requirements", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/olap-evaluation/performance-requirements/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/olap-evaluation/performance-requirements/1-benchmark-queries", - title: "Benchmark Queries", - languages: ["typescript", "python"], - }, - ], }, { type: "page", - slug: "guides/strategy/olap-evaluation/scale-requirements/overview", + slug: "guides/strategy/olap-evaluation/scale-requirements", title: "Scale Requirements", languages: ["typescript", "python"], - children: [ - { - type: "page", - slug: "guides/strategy/olap-evaluation/scale-requirements/requirements", - title: "Requirements", - languages: ["typescript", "python"], - }, - { - type: "page", - slug: "guides/strategy/olap-evaluation/scale-requirements/1-estimate-data-volume", - title: "Estimate Data Volume", - languages: ["typescript", "python"], - }, - ], }, ], }, diff --git a/apps/framework-docs-v2/src/lib/content.ts b/apps/framework-docs-v2/src/lib/content.ts index 861e1b7538..9097d01027 100644 --- a/apps/framework-docs-v2/src/lib/content.ts +++ b/apps/framework-docs-v2/src/lib/content.ts @@ -244,3 +244,52 @@ export function getAllSlugs(): string[] { const uniqueSlugs = Array.from(new Set(slugs)); return uniqueSlugs; } + +/** + * Discover step files in a directory + * Returns step files matching the pattern: {number}-{name}.mdx + * Sorted by step number + */ +export function discoverStepFiles(slug: string): Array<{ + slug: string; + stepNumber: number; + title: string; +}> { + const dirPath = path.join(CONTENT_ROOT, slug); + + if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) { + return []; + } + + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + const steps: Array<{ slug: string; stepNumber: number; title: string }> = []; + + for (const entry of entries) { + if (!entry.isFile()) continue; + + // Match pattern: {number}-{name}.mdx + const stepMatch = entry.name.match(/^(\d+)-(.+)\.mdx$/); + if (!stepMatch) continue; + + const stepNumber = parseInt(stepMatch[1]!, 10); + const stepName = stepMatch[2]; + if (!stepName) continue; + + const stepSlug = `${slug}/${entry.name.replace(/\.mdx$/, "")}`; + + // Read front matter to get title + const filePath = path.join(dirPath, entry.name); + const fileContents = fs.readFileSync(filePath, "utf8"); + const { data } = matter(fileContents); + const frontMatter = data as FrontMatter; + + steps.push({ + slug: stepSlug, + stepNumber, + title: (frontMatter.title as string) || stepName.replace(/-/g, " "), + }); + } + + // Sort by step number + return steps.sort((a, b) => a.stepNumber - b.stepNumber); +} From 227bd93cecf64284dfaac640dd1b378c4974ce98 Mon Sep 17 00:00:00 2001 From: Tim Delisle Date: Sun, 16 Nov 2025 17:35:51 -0800 Subject: [PATCH 07/10] fixed some styles --- apps/framework-docs-v2/package.json | 2 +- .../src/components/navigation/side-nav.tsx | 2 - .../src/components/navigation/toc-nav.tsx | 115 +++++++++++++++++- .../src/components/ui/popover.tsx | 33 +++++ pnpm-lock.yaml | 2 +- 5 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 apps/framework-docs-v2/src/components/ui/popover.tsx diff --git a/apps/framework-docs-v2/package.json b/apps/framework-docs-v2/package.json index 9780a46f10..14453c6d43 100644 --- a/apps/framework-docs-v2/package.json +++ b/apps/framework-docs-v2/package.json @@ -29,7 +29,7 @@ "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.2", - "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", diff --git a/apps/framework-docs-v2/src/components/navigation/side-nav.tsx b/apps/framework-docs-v2/src/components/navigation/side-nav.tsx index e3b5ef66bf..7df49d05a1 100644 --- a/apps/framework-docs-v2/src/components/navigation/side-nav.tsx +++ b/apps/framework-docs-v2/src/components/navigation/side-nav.tsx @@ -9,11 +9,9 @@ import { Sidebar, SidebarContent, SidebarGroup, - SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, - SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, diff --git a/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx b/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx index 0be4c2b7f6..f0fa5352f4 100644 --- a/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx +++ b/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx @@ -1,9 +1,29 @@ "use client"; import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; import { cn } from "@/lib/utils"; import type { Heading } from "@/lib/content-types"; -import { IconExternalLink } from "@tabler/icons-react"; +import { + IconExternalLink, + IconPlus, + IconInfoCircle, +} from "@tabler/icons-react"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Label } from "../ui/label"; interface TOCNavProps { headings: Heading[]; @@ -15,6 +35,10 @@ interface TOCNavProps { export function TOCNav({ headings, helpfulLinks }: TOCNavProps) { const [activeId, setActiveId] = useState(""); + const [scope, setScope] = useState<"initiative" | "project">("initiative"); + const pathname = usePathname(); + const isGuidePage = + pathname?.startsWith("/guides/") && pathname !== "/guides"; useEffect(() => { if (headings.length === 0) return; @@ -123,7 +147,7 @@ export function TOCNav({ headings, helpfulLinks }: TOCNavProps) { } return ( -
+ } + > + + + ); + }); return (
diff --git a/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx b/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx index f0fa5352f4..dabd27899c 100644 --- a/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx +++ b/apps/framework-docs-v2/src/components/navigation/toc-nav.tsx @@ -153,9 +153,9 @@ export function TOCNav({ headings, helpfulLinks }: TOCNavProps) {

On this page