npm package: @next-library/theme
Description: Complete UI theme for Next.js documentation framework
Author: FAKGROUP
Latest version: 1.0.4

# @next-library/theme

Complete UI theme package for Next.js documentation framework. This package provides ready-made React components built on top of [`@next-library/core`](../core/README.md).

> **Looking for core utilities only?** See [`@next-library/core`](../core/README.md).

> **Content tree:** Layout and nav expect the same **`PageData`** from **`extractPageData(contentTree, locale, slug)`**. Import **`contentTree`** from your generated **`lib/tree.ts`** and pass **`contentTree`** into **`NavigationBar`** (and derive **`getSupportedLocales(contentTree)`** for **`LanguageSwitcher`**). The canonical wiring is **[`example/app/`](../../example/app/)** and **[`docs/getting-started.md`](../../docs/getting-started.md)**.

## Features

- 🎨 **Complete UI Components** - Layout, navigation, markdown rendering, and more
- 🌍 **Multi-language Support** - Built-in translations for 20+ languages
- 📱 **Responsive Design** - Mobile-first with drawer navigation and responsive breadcrumbs
- 🎯 **Type-Safe** - Full TypeScript support
- ⚡ **Server/Client Separation** - Optimized for Next.js App Router
- 🎭 **Theme Support** - Dark mode with `next-themes`
- 📝 **Rich Markdown** - Syntax highlighting, math, diagrams, callouts

## Installation

```bash
# Using npm
npm install @next-library/theme @next-library/core

# Using pnpm
pnpm add @next-library/theme @next-library/core

# Using yarn
yarn add @next-library/theme @next-library/core
```

**Required peer dependencies:**
- `next-themes` - For theme switching (dark/light mode)
- `@tailwindcss/typography` - For markdown prose styling

```bash
pnpm add next-themes @tailwindcss/typography
```

## Quick Start

### 1. Set Up Tailwind CSS v4

The theme package uses Tailwind CSS v4 with `@source` directives (Nextra-style approach). Create or update your `globals.css`:

```css
/* app/globals.css */
@import 'tailwindcss';
@plugin "@tailwindcss/typography";

/* Import theme package styles to scan for Tailwind classes */
@import '@next-library/theme/style';

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

:root {
  /* Your CSS variables */
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  /* ... more variables */
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  /* ... more variables */
}
```

**Important:** The `@import '@next-library/theme/style'` line tells Tailwind where to scan for classes used in the theme components.

### 2. Set Up Theme Provider

Create a theme provider wrapper:

```typescript
// app/providers.tsx
'use client';

import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { ReactNode } from 'react';

export function ThemeProvider({ children }: { children: ReactNode }) {
  return (
    <NextThemesProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </NextThemesProvider>
  );
}
```

Wrap your app in `app/layout.tsx`:

```typescript
// app/layout.tsx
import { ThemeProvider } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
```

### 3. Use Components

#### Server Components

Import server components from `@next-library/theme/server`:

```typescript
// app/[locale]/layout.tsx
import { DocumentationLayout } from '@next-library/theme/server';
import { isValidLocale, type Locale } from '@next-library/core';

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale: raw } = await params;
  const locale = (isValidLocale(raw) ? raw : 'en') as Locale;

  return (
    <DocumentationLayout language={locale}>
      {children}
    </DocumentationLayout>
  );
}
```

#### Client Components

Import client components from `@next-library/theme/client`:

Server parent (derive locales from the generated tree):

```typescript
import { getSupportedLocales } from '@next-library/core';
import contentTree from '@/lib/tree';
import { LocaleTools } from './locale-tools';

const availableLocales = getSupportedLocales(contentTree);
// …
<LocaleTools availableLocales={availableLocales} />;
```

Client component:

```typescript
// app/locale-tools.tsx
'use client';

import { LanguageSwitcher, ThemeSwitcher } from '@next-library/theme/client';

export function LocaleTools({ availableLocales }: { availableLocales: string[] }) {
  return (
    <div>
      <LanguageSwitcher availableLocales={availableLocales} />
      <ThemeSwitcher />
    </div>
  );
}
```

`NavigationBar` computes locales from **`contentTree`** when you pass **`contentTree`** (see complete example below).

## Component Exports

### Server Components (`@next-library/theme/server`)

- `DocumentationLayout` - Main layout wrapper
- `NavigationBar` - Top navigation bar
- `LeftSidebar` - Left sidebar navigation
- `RightSidebar` - Right sidebar (TOC, contributors)
- `DocumentationFooter` - Footer component
- `StructuredData` - JSON-LD structured data renderer

### Client Components (`@next-library/theme/client`)

**Navigation:**
- `Banner` - Top banner component
- `Logo` - Logo component
- `TopCategories` - Top category navigation
- `SearchBar` - Search input with keyboard shortcuts
- `ThemeSwitcher` - Dark/light theme toggle
- `LanguageSwitcher` - Language selection dropdown
- `MobileMenu` - Mobile navigation drawer
- `GithubStar` - GitHub star button
- `GithubLink` - GitHub repository link
- `Breadcrumbs` - Breadcrumb navigation
- `BreadcrumbToggle` - Responsive breadcrumb overflow
- `FolderCards` - Grid of folder cards
- `SidebarNavigation` - Sidebar navigation tree
- `TableOfContents` - Table of contents
- `GithubContributors` - Contributors list
- `LevelElevator` - Current level indicator
- `AnchorLinkHandler` - Anchor link copying

**Markdown:**
- `MarkdownRenderer` - Markdown content renderer
- `CopyPageButton` - Copy page content button with ChatGPT/Claude integration
- `PageHeader` - Page title with copy button
- `FallbackLocaleWarning` - Warning for fallback locale content

**UI Primitives:**
- `Button`, `Card`, `Avatar`, `Collapsible`, `Drawer`, `DropdownMenu`, `Select`, `Separator`, `Sheet`, `Tooltip`, `Input`, `Skeleton`
- Sidebar components (`SidebarProvider`, `SidebarContent`, `SidebarMenu`, etc.)
- Breadcrumb components (`Breadcrumb`, `BreadcrumbItem`, etc.)

**Tracking:**
- `CookieBanner` - Cookie consent banner

**Hooks:**
- `useMediaQuery` - Media query hook
- `useIsMobile` - Mobile detection hook

## Complete Example

### Layout Structure

```typescript
// app/[locale]/layout.tsx
import { DocumentationLayout } from '@next-library/theme/server';
import { isValidLocale, type Locale } from '@next-library/core';

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale: rawLocale } = await params;
  const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;

  return (
    <DocumentationLayout language={locale}>
      {children}
    </DocumentationLayout>
  );
}
```

### Documentation Page Layout

```typescript
// app/[locale]/docs/[[...slug]]/layout.tsx
import { NavigationBar, LeftSidebar, RightSidebar } from '@next-library/theme/server';
import { Breadcrumbs, AnchorLinkHandler } from '@next-library/theme/client';
import { extractPageData, isValidLocale, createConfig, type Locale } from '@next-library/core';
import { libraryConfig } from '@/library.config';
import contentTree from '@/lib/tree';

export default async function DocsLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string; slug?: string[] }>;
}) {
  const { locale: rawLocale, slug } = await params;
  const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;
  const safeSlug = Array.isArray(slug) ? slug : [];
  const fullConfig = createConfig(libraryConfig);
  const pageData = extractPageData(contentTree, locale, safeSlug);
  const repoUrl =
    fullConfig.github?.user && fullConfig.github?.repo
      ? `https://github.com/${fullConfig.github.user}/${fullConfig.github.repo}`
      : undefined;

  return (
    <div>
      <NavigationBar
        language={locale}
        className="block lg:hidden"
        pageData={pageData}
        websiteTitle={fullConfig.website?.title || 'Documentation'}
        contentTree={contentTree}
        repoUrl={repoUrl}
      />
      <div className="flex flex-row gap-12 w-full lg:w-[95vw] mx-auto pt-10">
        <LeftSidebar
          data={pageData}
          className="sticky top-28 hidden lg:flex lg:flex-[2] max-w-xs"
        />
        <main className="flex flex-col lg:gap-8 gap-4 flex-1 w-full lg:flex-[6] px-4 lg:px-0">
          <Breadcrumbs data={pageData} locale={locale} />
          {children}
        </main>
        <RightSidebar
          data={pageData}
          className="sticky top-28 hidden lg:flex lg:flex-[1.5] max-w-xs"
        />
        <AnchorLinkHandler locale={locale} />
      </div>
    </div>
  );
}
```

### Documentation Page

```typescript
// app/[locale]/docs/[[...slug]]/page.tsx (leaf page — folder views need FolderCards; see example app)
import {
  extractPageData,
  isValidLocale,
  fetchRawFileContent,
  createConfig,
  type Locale,
} from '@next-library/core';
import { PageHeader, FallbackLocaleWarning } from '@next-library/theme/server';
import { MarkdownRenderer } from '@next-library/theme/client';
import { libraryConfig } from '@/library.config';
import contentTree from '@/lib/tree';
import { notFound } from 'next/navigation';

export default async function DocPage({
  params,
}: {
  params: Promise<{ locale: string; slug?: string[] }>;
}) {
  const { locale: rawLocale, slug } = await params;
  const locale = (isValidLocale(rawLocale) ? rawLocale : 'en') as Locale;
  const safeSlug = Array.isArray(slug) ? slug : [];
  const fullConfig = createConfig(libraryConfig);
  const pageData = extractPageData(contentTree, locale, safeSlug);
  const { node } = pageData;

  if (!node?.githubPath || !fullConfig.github?.user || !fullConfig.github?.repo) {
    notFound();
  }

  const githubConfig = {
    user: fullConfig.github.user,
    repo: fullConfig.github.repo,
    branch: fullConfig.github.branch || 'main',
    token: fullConfig.github.token,
    app: fullConfig.github.app,
  };
  const rawMarkdown = await fetchRawFileContent(node.githubPath, githubConfig);
  const baseUrl = fullConfig.website?.siteUrl || 'http://localhost:3000';
  const pageUrl = `${baseUrl.replace(/\/$/, '')}/${locale}/docs/${safeSlug.join('/')}`;
  const websiteTitle = fullConfig.website?.title || fullConfig.website?.organizationName || 'Documentation';

  return (
    <div>
      <PageHeader
        title={node.title}
        markdown={rawMarkdown}
        pageUrl={pageUrl}
        websiteTitle={websiteTitle}
        locale={locale}
      />
      {node.fallback && <FallbackLocaleWarning locale={locale} />}
      <MarkdownRenderer markdown={rawMarkdown} />
    </div>
  );
}
```

For **JSON-LD**, **`generateMetadata`**, **folder `index` views** (`FolderCards`), and error handling, copy from **`example/app/[locale]/docs/[[...slug]]/page.tsx`** in the monorepo.

## Internationalization

The theme package includes translations for 20+ languages. Components automatically use translations based on the `locale` prop:

```typescript
import { CopyPageButton } from '@next-library/theme/client';

// Uses default translations for locale
<CopyPageButton 
  sourceCode={markdown}
  locale="fr" // French translations will be used
/>

// Override specific translations
<CopyPageButton 
  sourceCode={markdown}
  locale="fr"
  translations={{
    copyPage: 'Copier la page',
    copied: 'Copié',
  }}
/>
```

### Supported Locales

- English (en)
- French (fr)
- German (de)
- Spanish (es)
- Arabic (ar) - RTL support
- Chinese (zh)
- Japanese (ja)
- Korean (ko)
- Portuguese (pt)
- Italian (it)
- Russian (ru)
- Dutch (nl)
- Swedish (sv)
- Norwegian (no)
- Danish (da)
- Finnish (fi)
- Polish (pl)
- Turkish (tr)
- Hebrew (he) - RTL support
- Hindi (hi)

### Custom Translations

All components accept an optional `translations` prop to override default translations:

```typescript
interface ComponentTranslations {
  // Component-specific translation keys
  copyPage?: string;
  copied?: string;
  // ... more keys
}
```

## Styling

### Tailwind CSS v4

The theme uses Tailwind CSS v4 with `@source` directives. Make sure to import the theme style file in your `globals.css`:

```css
@import '@next-library/theme/style';
```

This tells Tailwind where to scan for classes used in theme components.

### CSS Variables

The theme uses CSS variables for theming. Define your variables in `globals.css`:

```css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  /* ... more variables */
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  /* ... more variables */
}
```

### Customization

You can customize component styles by:

1. **Overriding CSS variables** - Change theme colors via CSS variables
2. **Using className prop** - Most components accept `className` for additional styling
3. **Tailwind classes** - All components use Tailwind, so you can override with utility classes

## Component Props

### DocumentationLayout

```typescript
interface DocumentationLayoutProps {
  children: React.ReactNode;
  language?: Locale;
  bannerMessage?: React.ReactNode | null;
  websiteTitle?: string;
}
```

### NavigationBar

```typescript
interface NavigationBarProps {
  language?: Locale;
  className?: string;
  pageData?: PageData;
  websiteTitle?: string;
  contentTree?: ContentTree;
  repoUrl?: string;
}
```

### PageHeader

```typescript
interface PageHeaderProps {
  title: string;
  markdown: string;
  pageUrl?: string;
  websiteTitle?: string;
  className?: string;
  locale?: Locale;
  translations?: CopyPageButtonTranslations;
  showCopyButton?: boolean;
}
```

### CopyPageButton

```typescript
interface CopyPageButtonProps {
  sourceCode: string;
  pageUrl?: string;
  websiteTitle?: string;
  className?: string;
  locale?: Locale;
  translations?: CopyPageButtonTranslations;
}
```

The `CopyPageButton` includes integration with ChatGPT and Claude:
- **Copy page** - Copies markdown content to clipboard
- **Open in ChatGPT** - Opens ChatGPT with prefilled message including website title and URL
- **Open in Claude** - Opens Claude with prefilled message including website title and URL

## Server/Client Separation

The theme package properly separates server and client components:

- **Server components** are imported from `@next-library/theme/server`
- **Client components** are imported from `@next-library/theme/client`
- Server components can import client components (they're externalized in the build)

This ensures optimal performance with Next.js App Router.

## TypeScript Support

The package is fully typed. Import types as needed:

```typescript
import type {
  DocumentationLayoutProps,
  NavigationBarProps,
  PageHeaderProps,
  CopyPageButtonProps,
} from '@next-library/theme/server';
```

## Examples

See the [`example`](../../example/) directory for a complete working example.

## API Reference

### Server Components

- `DocumentationLayout` - Main layout wrapper
- `NavigationBar` - Top navigation
- `LeftSidebar` - Left sidebar navigation
- `RightSidebar` - Right sidebar (TOC, contributors)
- `DocumentationFooter` - Footer
- `StructuredData` - JSON-LD renderer

### Client Components

See the [Component Exports](#component-exports) section above for the complete list.

## Contributing

Contributions are welcome! Please see our [contributing guide](../../CONTRIBUTING.md) for details.

## License

MIT

## Support

- [Documentation](https://docs.example.com)
- [GitHub Issues](https://github.com/your-org/your-library/issues)
- [Discussions](https://github.com/your-org/your-library/discussions)