npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hillnote/wiki

v0.2.7

Published

Hillnote Wiki components for Next.js projects

Readme

@hillnote/wiki

Turn your Hillnote workspace into a beautiful wiki for your Next.js projects. This package provides ready-to-use components for creating documentation sites powered by your Hillnote workspace.

Features

  • 📚 Beautiful documentation UI with navigation sidebar
  • 🎨 Dark/Light theme support
  • 📱 Fully responsive design
  • 🔍 SEO-friendly with sitemap generation
  • 🤖 AI-crawler optimized with structured data
  • 📁 Auto-expanding navigation for active documents
  • 🔗 Smart URL routing with slugs
  • 📝 Markdown rendering with syntax highlighting

Installation

npm install @hillnote/wiki
# or
yarn add @hillnote/wiki
# or
pnpm add @hillnote/wiki

Note: All required dependencies (@radix-ui components, gray-matter, etc.) will be automatically installed with the package.

Quick Start

1. Setup Root Layout with Theme Provider

First, create a providers component and update your root layout:

// app/providers.js
"use client"

import { ThemeProvider } from '@hillnote/wiki'

export function Providers({ children }) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  )
}
// app/layout.js
import { Providers } from "./providers"
import "@hillnote/wiki/styles"
import "./globals.css"

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

2. Create Your Wiki Pages

Create the main wiki page and dynamic routing:

// app/page.js
"use client"

import { Document, Navbar, ConfigProvider, pathToSlug, initializeSlugMapping } from '@hillnote/wiki'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'

const wikiConfig = {
  siteName: "My Wiki",
  workspace: {
    path: "/workspace/", // Path to your Hillnote workspace in public folder
    enabled: true,
    documentsFolder: "documents",
    registryFile: "documents-registry.json",
    initialFile: "documents/Start Here.md"
  },
  ui: {
    authorsNotes: true,
    navigationText: "All Pages"
  }
}

export default function WikiPage() {
  const [mounted, setMounted] = useState(false)
  const router = useRouter()

  useEffect(() => {
    setMounted(true)
    // Initialize slug mapping on mount
    initializeSlugMapping(wikiConfig)
  }, [])

  const handleFileSelect = (filePath) => {
    if (!filePath) return
    
    // Convert file path to URL slug
    const slug = pathToSlug(filePath)
    
    router.push(`/doc/${slug}`)
  }

  if (!mounted) return null

  return (
    <ConfigProvider config={wikiConfig}>
      <div className="h-screen flex flex-col">
        <Navbar siteName={wikiConfig.siteName} showThemeToggle={true} />
        <Document 
          siteConfig={wikiConfig}
          initialFile={wikiConfig.workspace.initialFile}
          showNavigation={true}
          showTableOfContents={true}
          onFileSelect={handleFileSelect}
        />
      </div>
    </ConfigProvider>
  )
}
// app/doc/[...path]/page.js
"use client"

import { Document, Navbar, ConfigProvider, pathToSlug, slugToPath, initializeSlugMapping } from '@hillnote/wiki'
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'

const wikiConfig = {
  siteName: "My Wiki",
  workspace: {
    path: "/workspace/",
    enabled: true,
    documentsFolder: "documents",
    registryFile: "documents-registry.json",
    initialFile: "documents/Start Here.md"
  },
  ui: {
    authorsNotes: true,
    navigationText: "All Pages"
  }
}

export default function DocumentPage() {
  const params = useParams()
  const router = useRouter()
  const [mounted, setMounted] = useState(false)
  const [filePath, setFilePath] = useState(wikiConfig.workspace.initialFile)
  
  // Get the slug from URL params
  const slug = params.path ? params.path.join('/') : null

  useEffect(() => {
    setMounted(true)
    // Initialize slug mapping and then set the file path
    const initAndSetPath = async () => {
      await initializeSlugMapping(wikiConfig)
      if (slug) {
        const path = slugToPath(slug)
        setFilePath(path)
      }
    }
    initAndSetPath()
  }, [slug])

  const handleFileSelect = (filePath) => {
    if (!filePath) return
    
    // Convert file path to URL slug
    const slug = pathToSlug(filePath)
    
    router.push(`/doc/${slug}`)
  }

  if (!mounted) return null

  return (
    <ConfigProvider config={wikiConfig}>
      <div className="h-screen flex flex-col">
        <Navbar siteName={wikiConfig.siteName} showThemeToggle={true} />
        <Document 
          siteConfig={wikiConfig}
          initialFile={filePath}
          showNavigation={true}
          showTableOfContents={true}
          onFileSelect={handleFileSelect}
        />
      </div>
    </ConfigProvider>
  )
}

3. Copy Your Hillnote Workspace

Copy your Hillnote workspace to your Next.js public folder:

cp -r ~/path-to-hillnote-workspace public/workspace

4. Setup Styles and Tailwind

  1. Update your globals.css with theme variables:
/* app/globals.css */
@import "tailwindcss";

:root {
  --background: 0 0% 100%;
  --foreground: 0 0% 3.9%;
  --card: 0 0% 100%;
  --card-foreground: 0 0% 3.9%;
  --popover: 0 0% 100%;
  --popover-foreground: 0 0% 3.9%;
  --primary: 0 0% 9%;
  --primary-foreground: 0 0% 98%;
  --secondary: 0 0% 96.1%;
  --secondary-foreground: 0 0% 9%;
  --muted: 0 0% 96.1%;
  --muted-foreground: 0 0% 45.1%;
  --accent: 0 0% 96.1%;
  --accent-foreground: 0 0% 9%;
  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 0 0% 98%;
  --border: 0 0% 89.8%;
  --input: 0 0% 89.8%;
  --ring: 0 0% 3.9%;
  --radius: 0.5rem;
}

.dark {
  --background: 0 0% 3.9%;
  --foreground: 0 0% 98%;
  --card: 0 0% 3.9%;
  --card-foreground: 0 0% 98%;
  --popover: 0 0% 3.9%;
  --popover-foreground: 0 0% 98%;
  --primary: 0 0% 98%;
  --primary-foreground: 0 0% 9%;
  --secondary: 0 0% 14.9%;
  --secondary-foreground: 0 0% 98%;
  --muted: 0 0% 14.9%;
  --muted-foreground: 0 0% 63.9%;
  --accent: 0 0% 14.9%;
  --accent-foreground: 0 0% 98%;
  --destructive: 0 62.8% 30.6%;
  --destructive-foreground: 0 0% 98%;
  --border: 0 0% 14.9%;
  --input: 0 0% 14.9%;
  --ring: 0 0% 83.1%;
}

body {
  background: hsl(var(--background));
  color: hsl(var(--foreground));
}
  1. Create or update your tailwind.config.js:
// tailwind.config.js
module.exports = {
  darkMode: ["class"],
  content: [
    // ... your other content paths
    "./node_modules/@hillnote/wiki/dist/**/*.{js,mjs}"
  ],
  theme: {
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [],
}

Components

Navbar

The navigation bar component for your wiki.

import { Navbar } from '@hillnote/wiki'

<Navbar 
  siteName="My Wiki"
  showSiteName={true}
  showThemeToggle={true}
  className="custom-navbar"
>
  {/* Additional navbar items */}
</Navbar>

Document

The main document viewer with navigation sidebar and table of contents.

import { Document } from '@hillnote/wiki'

<Document 
  siteConfig={wikiConfig}           // Pass the entire config object
  initialFile="documents/index.md"  // Initial file to display
  showNavigation={true}              // Show navigation sidebar
  showTableOfContents={true}         // Show table of contents
  onFileSelect={(file) => console.log('Selected:', file)}  // File selection handler
/>

TableOfContents

A standalone table of contents component.

import { TableOfContents } from '@hillnote/wiki'

<TableOfContents 
  showTitle={true}
  title="Contents"
/>

Configuration

The configuration object supports the following options:

const wikiConfig = {
  siteName: "My Documentation",
  workspace: {
    path: "/workspace/",              // Path to workspace in public folder
    enabled: true,
    documentsFolder: "documents",     // Folder containing markdown files
    registryFile: "documents-registry.json",  // Registry file name
    initialFile: "documents/Start Here.md"    // Initial document to display
  },
  ui: {
    authorsNotes: true,               // Enable author's notes section
    navigationText: "All Pages",      // Navigation sidebar title
    navigationMode: "wiki"            // "emoji" or "wiki" (accordion style)
  }
}

Navigation Modes

The sidebar navigation supports two different display styles. You can switch between them using the navigationMode setting in your configuration.

1. Emoji Mode (Default)

Traditional file explorer style with emoji icons and arrow indicators.

Features:

  • 📁 Folder icons for directories
  • 📄 File icons for documents
  • ▶️ / ▼ Arrow indicators for expand/collapse
  • Visual hierarchy with indentation
const wikiConfig = {
  ui: {
    navigationMode: "emoji"  // or omit for default
  }
}

2. Wiki Mode

Clean, modern accordion-style navigation similar to popular documentation sites.

Features:

  • Clean text-based navigation
  • Smooth accordion expand/collapse animations
  • No visual clutter from icons
  • Folders stay open when selecting files inside them
  • Auto-expands to show the current document
const wikiConfig = {
  ui: {
    navigationMode: "wiki"
  }
}

Complete example with wiki mode:

const wikiConfig = {
  siteName: "My Documentation",
  workspace: {
    path: "/workspace/",
    enabled: true,
    documentsFolder: "documents",
    registryFile: "documents-registry.json",
    initialFile: "documents/Start Here.md"
  },
  ui: {
    authorsNotes: true,
    navigationText: "All Pages",
    navigationMode: "wiki"  // Set to "wiki" for accordion style
  }
}

Advanced Usage

Custom Layout

You can compose the components to create custom layouts:

import { useState } from 'react'
import { Navbar, Document, MarkdownRenderer, NavigationSidebar } from '@hillnote/wiki'

export default function CustomWiki() {
  const [selectedFile, setSelectedFile] = useState(null)
  
  return (
    <div className="custom-layout">
      <Navbar siteName="My Wiki" />
      
      <div className="flex">
        <NavigationSidebar 
          onFileSelect={setSelectedFile}
          selectedFile={selectedFile}
        />
        
        <main className="flex-1">
          <MarkdownRenderer 
            filePath={selectedFile}
            onFileSelect={setSelectedFile}
          />
        </main>
      </div>
    </div>
  )
}

URL Routing with Slugs

The package includes utilities for converting between file paths and URL-friendly slugs:

import { pathToSlug, slugToPath, initializeSlugMapping } from '@hillnote/wiki'

// Initialize slug mapping (required once on app mount)
await initializeSlugMapping(wikiConfig)

// Convert file path to URL slug
const slug = pathToSlug("documents/Getting Started.md")
// Returns: "getting-started"

// Convert slug back to file path
const path = slugToPath("getting-started")
// Returns: "documents/Getting Started.md"

Workspace Utilities

The package exports utilities for working with your Hillnote workspace:

import { buildFileTree, fetchWorkspaceRegistry, getWorkspaceFileTree } from '@hillnote/wiki'

// Fetch the workspace registry
const registry = await fetchWorkspaceRegistry(wikiConfig)

// Build a hierarchical file tree from the registry
const fileTree = buildFileTree(registry.documents)

// Get the complete workspace file tree
const tree = await getWorkspaceFileTree(wikiConfig)

Troubleshooting

Hydration Errors

If you encounter hydration errors with Next.js, ensure you're using the client-side mounting pattern shown in the Quick Start example. The mounted state prevents theme-related attributes from causing mismatches between server and client rendering.

Workspace Structure

Your Hillnote workspace should have this structure in your public folder:

public/
└── workspace/
    ├── documents/
    │   ├── Getting Started.md
    │   ├── Installation.md
    │   └── ...
    └── documents-registry.json

The documents-registry.json file should contain metadata about your documents:

{
  "documents": [
    {
      "path": "documents/Getting Started.md",
      "title": "Getting Started",
      "lastModified": "2024-01-01T00:00:00Z"
    }
  ]
}

JavaScript/TypeScript Support

The package works with both JavaScript and TypeScript. For TypeScript users, types are available:

import type { 
  NavbarProps, 
  DocumentProps, 
  TableOfContentsProps,
  HillnoteWikiConfig 
} from '@hillnote/wiki'

For JavaScript users, you can use JSDoc comments for type hints:

/** @type {import('@hillnote/wiki').HillnoteWikiConfig} */
const wikiConfig = {
  // your configuration
}

Requirements

  • Next.js 13+ (with App Router)
  • React 18+
  • Tailwind CSS (for styling)
  • A Hillnote workspace

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please visit our GitHub repository.