Skip to content

Repository files navigation

Docs Module Documentation

Overview

The src/module/docs module is a complete documentation system built with React, MDX, and Vite. It provides live component previews, multi-package-manager support, syntax highlighting, and a component registry for showcasing UI components.

This documentation system is designed with functional programming principles, type safety, and modularity at its core.

Architecture Overview

The docs module follows a clean, layered architecture:

graph TD
    DocsPage[DocsPage Component] --> DocsLoader[docs-loader.ts]
    DocsPage --> MDXProvider[MDX Provider]
    MDXProvider --> DocComponents[Doc Components]
    DocComponents --> Preview[ComponentPreview]
    DocComponents --> Source[ComponentSource]
    DocComponents --> Usage[ComponentUsage]
    Preview --> Registry[registry-vite.ts]
    Source --> Registry
    Usage --> Registry
    Registry --> UIComponents[UI Components]
    Registry --> Examples[Example Components]
Loading

Key Architectural Decisions

  • MDX-based Content System: Uses Vite's import.meta.glob for automatic file discovery and routing
  • Component Registry Pattern: Centralized registry with lazy loading for optimal performance
  • Client/Server Separation: Clear separation between server-side rendering and client-side hydration
  • Type-Safe Helpers: All utilities leverage TypeScript generics and functional programming patterns

File Structure

src/module/docs/
├── page.tsx                           # Main docs page component
├── components/                        # Documentation UI components
│   ├── component-preview-vite.tsx     # Live preview with code display
│   ├── component-preview-client.tsx   # Client-side preview wrapper
│   ├── component-source-vite.tsx      # Source code display
│   ├── component-source-client.tsx    # Client-side source wrapper
│   ├── component-usage-vite.tsx       # Usage example display
│   ├── component-usage-client.tsx     # Client-side usage wrapper
│   ├── code-tabs.tsx                  # Installation method switcher
│   ├── code-block-command.tsx         # Multi-package-manager commands
│   ├── design-system.tsx              # Complete design system showcase
│   └── docs-page-actions.tsx          # Page navigation and copy actions
├── content/                           # MDX documentation files
│   ├── index.mdx                      # Documentation home page
│   ├── button-component.mdx           # Button component docs
│   └── system-design.mdx              # Design system overview
├── helpers/                           # Utility functions
│   ├── docs-loader.ts                 # MDX file loading and path resolution
│   ├── highlight-code.ts              # Shiki syntax highlighting
│   ├── registry-vite.ts               # Component registry and dependencies
│   └── use-config.ts                  # User preferences management
└── registry/                          # Component registry
    ├── entry.tsx                      # Registry definitions with lazy loading
    └── examples/                      # Component examples and demos
        ├── button-demo.tsx
        └── button-usage.tsx

Core Components

DocsPage

The main page component that renders MDX documentation with all necessary providers.

Location: src/module/docs/page.tsx

Features:

  • Automatic path resolution from URL
  • MDX provider with custom components
  • Error handling for missing pages
  • Header with metadata display

Usage in Router:

{
  path: "docs/*",
  element: <DocsPage />
}

ComponentPreview

Displays a live, interactive preview of a component with optional source code.

Props:

  • name: string - Component name from registry
  • className?: string - Additional CSS classes
  • align?: "center" | "start" | "end" - Preview alignment
  • hideCode?: boolean - Hide the code section

Usage in MDX:

<ComponentPreview name="button-demo" />
<ComponentPreview name="button-demo" align="start" hideCode />

ComponentSource

Shows only the source code of a component with syntax highlighting.

Props:

  • name: string - Component name from registry
  • className?: string - Additional CSS classes
  • collapsible?: boolean - Make code collapsible (default: true)

Usage in MDX:

<ComponentSource name="button" />

ComponentUsage

Displays a usage example with syntax highlighting.

Props:

  • name: string - Usage example name from registry
  • className?: string - Additional CSS classes

Usage in MDX:

<ComponentUsage name="button-usage" />

CodeTabs

Wrapper for installation instruction tabs that syncs with user preferences.

Usage in MDX:

<CodeTabs>
  <TabsList>
    <TabsTrigger value="cli">CLI</TabsTrigger>
    <TabsTrigger value="manual">Manual</TabsTrigger>
  </TabsList>
  <TabsContent value="cli">```bash pnpm add my-component ```</TabsContent>
  <TabsContent value="manual">Copy the component code...</TabsContent>
</CodeTabs>

CodeBlockCommand

Automatically converts package manager commands and displays them in tabs.

How it works: The highlight-code.ts helper automatically detects package manager commands and creates variants. The component displays these in a tabbed interface.

Supported commands:

  • npm install / pnpm add / yarn add / bun add
  • npm create / pnpm create / yarn create / bun create
  • npx / pnpm dlx / yarn dlx / bunx --bun
  • npm run / pnpm run / yarn / bun run

DesignSystem

Complete showcase of the design system including colors, typography, and all UI components.

Usage in MDX:

<DesignSystem />

Helper Functions

docs-loader.ts

Handles automatic MDX file loading and path resolution.

Key Functions:

// Get all documentation pages
function getAllDocPages(): DocPage[];

// Get all documentation paths for routing
function getAllDocPaths(): string[];

// Get a single doc page by path
function getDocByPath(docPath: string): DocPage | undefined;

Path Resolution:

  • /src/module/docs/content/index.mdx/
  • /src/module/docs/content/components/button.mdx/components/button

highlight-code.ts

Provides syntax highlighting with Shiki and package manager command detection.

Key Functions:

// Highlight code with dual theme support
async function highlightCode(code: string, lang?: string): Promise<string>;

Features:

  • Dual theme support (light/dark)
  • Automatic package manager variant detection
  • Line number support
  • Custom transformers for code metadata

registry-vite.ts

Component registry system for loading UI components and examples.

Key Functions:

// Get registry item by name (UI component or example)
function getRegistryItem(name: string): RegistryItem | null;

Registry Item Types:

  • registry:ui - UI components from src/components/ui/
  • registry:example - Component demos
  • registry:usage - Usage examples

Automatic Dependency Detection: Extracts dependencies from import statements like from "@/components/ui/button".

use-config.ts

User preference management using Jotai for state persistence.

Configuration:

type Config = {
  installationType: "cli" | "manual";
  packageManager: "npm" | "pnpm" | "yarn" | "bun";
};

Usage:

const [config, setConfig] = useConfig();

// Update package manager
setConfig({ ...config, packageManager: "pnpm" });

Usage Guide

Adding New Documentation Pages

  1. Create an MDX file in src/module/docs/content/:
---
title: Card Component
description: A versatile card component for content containers
---

# Card Component

The Card component is a flexible container for grouping related information.

## Preview

<ComponentPreview name="card-demo" />

## Installation

<CodeTabs>
  <TabsContent value="cli">```bash pnpm add @/components/ui/card ```</TabsContent>
</CodeTabs>

## Usage

<ComponentUsage name="card-usage" />

## API

### Card

| Prop      | Type      | Default | Description            |
| --------- | --------- | ------- | ---------------------- |
| className | string    | -       | Additional CSS classes |
| children  | ReactNode | -       | Card content           |
  1. Access the page: The file-based routing automatically registers the page at /:lang/docs/[path]

Adding Components to Registry

  1. Create a component example in src/module/docs/registry/examples/:
// card-demo.tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

export default function CardDemo() {
  return (
    <Card className="w-80">
      <CardHeader>
        <CardTitle>Card Title</CardTitle>
        <CardDescription>Card description goes here</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Card content goes here.</p>
        <Button className="mt-4">Action</Button>
      </CardContent>
    </Card>
  );
}
  1. Register in src/module/docs/registry/entry.tsx:
const Registry: Record<string, RegistryEntry> = {
  // ... existing entries
  "card-demo": {
    name: "card-demo",
    type: "registry:example",
    files: [
      {
        path: "src/module/docs/registry/examples/card-demo.tsx",
        type: "registry:example"
      }
    ],
    component: React.lazy(() => import("@docs/registry/examples/card-demo").then((m) => ({ default: m.default }))),
    registryDependencies: ["card", "button"]
  }
};
  1. Use in MDX:
<ComponentPreview name="card-demo" />

Using Doc Components in MDX

All components are automatically available in MDX files without imports.

Live Preview with Code

<ComponentPreview name="button-demo" />

Live Preview Without Code

<ComponentPreview name="button-demo" hideCode />

Source Code Only

<ComponentSource name="button" />

Usage Example

<ComponentUsage name="button-usage" />

Installation Tabs

<CodeTabs>
  <TabsList>
    <TabsTrigger value="cli">CLI</TabsTrigger>
    <TabsTrigger value="manual">Manual</TabsTrigger>
  </TabsList>
  <TabsContent value="cli">Install via CLI...</TabsContent>
  <TabsContent value="manual">Copy the code...</TabsContent>
</CodeTabs>

Steps

<Steps>
  <Step>First, install the dependencies</Step>
  <Step>Then, create the component</Step>
  <Step>Finally, use it in your app</Step>
</Steps>

Tabs

<Tabs defaultValue="preview">
  <TabsList>
    <TabsTrigger value="preview">Preview</TabsTrigger>
    <TabsTrigger value="code">Code</TabsTrigger>
  </TabsList>
  <TabsContent value="preview">Preview content...</TabsContent>
  <TabsContent value="code">Code content...</TabsContent>
</Tabs>

Technical Details

Routing Integration

The docs module integrates with React Router using a catch-all route:

{
  path: "docs/*",
  element: <DocsPage />
}

URL structure: /:lang/docs/[...path]

Examples:

  • /en/docs → Landing page (index.mdx)
  • /en/docs/components/button → Button component docs

Syntax Highlighting

Engine: Shiki with dual theme support

Themes:

  • Light: github-light
  • Dark: github-dark

Features:

  • Line numbers
  • Package manager command detection
  • Custom transformers for metadata

Package Manager Detection

The system automatically detects and converts commands:

Input (in MDX):

pnpm add react

Output: Tabs with all variants:

  • npm: npm install react
  • pnpm: pnpm add react
  • yarn: yarn add react
  • bun: bun add react

Lazy Loading

All registry components use React.lazy for code splitting:

component: React.lazy(() => import("@docs/registry/examples/button-demo").then((m) => ({ default: m.default })));

Type Safety

Full TypeScript coverage with strict types:

type DocMetadata = {
  title: string;
  description?: string;
};

type DocPage = {
  path: string;
  metadata: DocMetadata;
  Component: React.ComponentType;
};

type RegistryItem = {
  name: string;
  type: "registry:ui" | "registry:example" | "registry:usage";
  files: Array<{
    path: string;
    content: string;
    type: "registry:ui" | "registry:example" | "registry:usage";
  }>;
  registryDependencies?: string[];
};

Configuration

User Preferences

Stored in localStorage via Jotai with key r1cco-docs-config:

{
  installationType: "cli" | "manual",
  packageManager: "npm" | "pnpm" | "yarn" | "bun"
}

Default values:

  • installationType: "cli"
  • packageManager: "pnpm"

Customizing Preferences

Users can change preferences through:

  1. Package manager tabs (automatically saved)
  2. Installation type tabs (automatically saved)

Examples

Complete Component Documentation Example

---
title: Tooltip Component
description: A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
---

# Tooltip

<ComponentPreview name="tooltip-demo" />

## Installation

<CodeTabs>
  <TabsList>
    <TabsTrigger value="cli">CLI</TabsTrigger>
    <TabsTrigger value="manual">Manual</TabsTrigger>
  </TabsList>
  <TabsContent value="cli">```bash pnpm add @radix-ui/react-tooltip ```</TabsContent>
  <TabsContent value="manual">
    <Steps>
      <Step>Install Radix UI Tooltip</Step>
      <Step>Copy the component code below</Step>
      <Step>Paste into `src/components/ui/tooltip.tsx`</Step>
    </Steps>

    <ComponentSource name="tooltip" />

  </TabsContent>
</CodeTabs>

## Usage

<ComponentUsage name="tooltip-usage" />

## API Reference

### Tooltip

| Prop              | Type      | Default | Description                             |
| ----------------- | --------- | ------- | --------------------------------------- |
| children          | ReactNode | -       | Tooltip trigger and content             |
| delayDuration     | number    | 200     | Delay before showing tooltip            |
| skipDelayDuration | number    | 300     | Skip delay when moving between tooltips |

## Accessibility

- Follows WAI-ARIA authoring practices
- Keyboard accessible (focus trigger)
- Screen reader announcements

Registry Entry Example

// In src/module/docs/registry/entry.tsx

const Registry: Record<string, RegistryEntry> = {
  "tooltip-demo": {
    name: "tooltip-demo",
    type: "registry:example",
    files: [
      {
        path: "src/module/docs/registry/examples/tooltip-demo.tsx",
        type: "registry:example"
      }
    ],
    component: React.lazy(() => import("@docs/registry/examples/tooltip-demo").then((m) => ({ default: m.default }))),
    registryDependencies: ["tooltip", "button"]
  },

  "tooltip-usage": {
    name: "tooltip-usage",
    type: "registry:usage",
    files: [
      {
        path: "src/module/docs/registry/examples/tooltip-usage.tsx",
        type: "registry:usage"
      }
    ],
    component: React.lazy(() => import("@docs/registry/examples/tooltip-usage").then((m) => ({ default: m.default }))),
    registryDependencies: ["tooltip"]
  }
};

Component Example

// In src/module/docs/registry/examples/tooltip-demo.tsx

import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

export default function TooltipDemo() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger asChild>
          <Button variant="outline">Hover me</Button>
        </TooltipTrigger>
        <TooltipContent>
          <p>This is a tooltip</p>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
}

Best Practices

Documentation Writing

  1. Clear Titles and Descriptions: Use descriptive frontmatter

    ---
    title: Button Component
    description: A versatile button component with multiple variants and sizes
    ---
  2. Show, Don't Tell: Lead with live previews

    <ComponentPreview name="button-demo" />
  3. Provide Multiple Views: Show preview, source, and usage

    <ComponentPreview name="button-demo" />
    <ComponentSource name="button" />
    <ComponentUsage name="button-usage" />
  4. Document API Thoroughly: Include prop tables

    | Prop | Type | Default | Description |
    | ---- | ---- | ------- | ----------- |

Component Examples

  1. Keep Examples Minimal: Focus on a single concept per example

    // Good: Focused example
    export default function ButtonDemo() {
      return <Button>Click me</Button>;
    }
    
    // Bad: Too many concepts
    export default function ButtonDemo() {
      const [count, setCount] = useState(0);
      const handleClick = () => setCount(c => c + 1);
      return (
        <>
          <Button onClick={handleClick}>Clicked {count} times</Button>
          <Button variant="secondary">Secondary</Button>
          <Button size="lg">Large</Button>
        </>
      );
    }
  2. Use Proper TypeScript Types: No implicit any

    // Good
    type DemoProps = {
      variant?: "default" | "outline";
    };
    
    export default function Demo({ variant = "default" }: DemoProps) {
      return <Button variant={variant}>Click</Button>;
    }
  3. Follow Functional Programming: Pure functions, immutability

    // Good: Pure component
    export default function Demo() {
      return <Button>Click</Button>;
    }
    
    // Acceptable: Local state only
    export default function Demo() {
      const [open, setOpen] = React.useState(false);
      return <Button onClick={() => setOpen(true)}>Open</Button>;
    }

Registry Management

  1. Consistent Naming: Use kebab-case

    • button-demo
    • ButtonDemo
    • button_demo
  2. Proper Type Classification:

    • Use registry:example for demos
    • Use registry:usage for usage examples
    • Use registry:ui for source components
  3. Declare Dependencies: Always list registry dependencies

    registryDependencies: ["button", "card", "tooltip"];
  4. Single Responsibility: One concept per registry entry

Code Organization

  1. Colocate Related Files: Keep examples with their registry entries

  2. Separate Client/Server Logic: Use -client and -vite suffixes

    • component-preview-vite.tsx - Server-side data loading
    • component-preview-client.tsx - Client-side interactivity
  3. Leverage Type Exports: Export types for reuse

    export type { ComponentPreviewProps };
  4. Use Path Aliases: Consistent import paths

    import { Button } from "@/components/ui/button";
    import { ComponentPreview } from "@docs/components/component-preview-vite";

Troubleshooting

Component Not Found in Registry

Error: "Component not found: button-demo"

Solution: Ensure the component is registered in src/module/docs/registry/entry.tsx

MDX Page Not Loading

Error: "Documentation not found"

Solution:

  1. Check file path: src/module/docs/content/[your-file].mdx
  2. Verify frontmatter is present and valid
  3. Check URL matches file structure

Syntax Highlighting Not Working

Solution: Verify code blocks have language specified:

\`\`\`typescript
const hello = "world";
\`\`\`

Package Manager Tabs Not Showing

Solution: Ensure command starts with a recognized pattern:

  • pnpm add
  • npm install
  • yarn add
  • bun add

Contributing

When contributing to the docs module:

  1. Follow the Architecture: Maintain separation between server and client components
  2. Add Types: All new functions and components must be fully typed
  3. Write Examples: Include practical examples for new features
  4. Update This README: Document any new patterns or components
  5. Test Thoroughly: Verify in both light and dark modes
  6. Consider i18n: Ensure content works across locales

License

This documentation system is part of the r1cco portfolio project.

About

Personal portfolio at r1cco.com. React/Vite SPA with blog and design-system docs.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages