Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/package_compat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Package Compatibility

on:
push:
branches-ignore:
- main
- next
- alpha
- beta
- '*.x*'
pull_request:
branches:
- main
- next
- alpha
- beta
- '*.x*'

jobs:
package-compat:
name: Packages -> React ${{ matrix.react_label }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- react_label: 18
react: 18.3.1
react_dom: 18.3.1
types_react: 18.3.12
types_react_dom: 18.3.1
- react_label: 19
react: 19.2.4
react_dom: 19.2.4
types_react: 19.2.14
types_react_dom: 19.2.3

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup LTS Node
uses: actions/setup-node@v6
with:
node-version: 'lts/*'
cache: 'yarn'

- name: Pin React toolchain
run: >
node scripts/set-react-toolchain.mjs
${{ matrix.react }}
${{ matrix.react_dom }}
${{ matrix.types_react }}
${{ matrix.types_react_dom }}

- name: Install dependencies
run: yarn install

- name: Typecheck published packages
run: yarn typecheck:packages

- name: Build published packages
run: yarn build

- name: Test published packages
run: yarn test:packages
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Thumbs.db

# Next.js
.next
apps/docs/out
*.tsbuildinfo

# Nx
Expand Down
217 changes: 217 additions & 0 deletions apps/docs/app/[[...mdxPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { generateStaticParamsFor, importPage } from 'nextra/pages';
import { SkipNavContent } from 'nextra/components';
import { getPageMap } from 'nextra/page-map';
import { Layout } from 'nextra-theme-docs';
import { useMDXComponents as getMDXComponents } from '../../mdx-components';
import {
banner,
docsRepositoryBase,
footer,
navbar,
search,
} from '../../theme.config';

type PageParams = {
mdxPath?: string[];
};

type ContentSection = 'docs' | 'api-reference' | 'top-level';
type SidebarPageMapItem = {
name: string;
route: string;
title?: string;
children?: SidebarPageMapItem[];
frontMatter?: unknown;
theme?: {
collapsed?: boolean;
};
};
type SidebarPageMap = SidebarPageMapItem[];

export const generateStaticParams = generateStaticParamsFor('mdxPath');

export async function generateMetadata(props: {
params: Promise<PageParams>;
}) {
const params = await props.params;
const { metadata } = await importPage(params.mdxPath);
return metadata;
}

const Wrapper = getMDXComponents().wrapper;

function getContentSection(mdxPath: string[] = []): ContentSection {
const [section] = mdxPath;

if (section === 'docs') {
return 'docs';
}

if (section === 'api-reference') {
return 'api-reference';
}

return 'top-level';
}

function getPageMapRoute(section: ContentSection) {
if (section === 'docs') {
return '/docs';
}

if (section === 'api-reference') {
return '/api-reference';
}

return null;
}

function getArticleClass(mdxPath: string[] = []) {
const [section] = mdxPath;
const isArticlePage = section === 'about' || section === 'showcase';

return [
'x:w-full x:min-w-0 x:break-words',
'x:min-h-[calc(100vh-var(--nextra-navbar-height))]',
'x:text-slate-700 x:dark:text-slate-200 x:pb-8 x:px-4 x:pt-4 x:md:px-12',
isArticlePage ? 'nextra-body-typesetting-article' : '',
]
.filter(Boolean)
.join(' ');
}

function findPageMapItem(
items: SidebarPageMapItem[],
name: string,
options?: { children?: boolean }
) {
const item = items.find((candidate) => candidate.name === name);

if (!item) {
throw new Error(`Missing page map item "${name}"`);
}

if (options?.children && !('children' in item)) {
throw new Error(`Expected "${name}" to have children`);
}

return item;
}

function withTitle(item: SidebarPageMapItem, title: string) {
return {
...item,
title,
};
}

async function getDocsSidebarPageMap(): Promise<SidebarPageMap> {
const docsPageMap = (await getPageMap('/docs')) as SidebarPageMap;
const tutorial = findPageMapItem(docsPageMap, 'tutorial', { children: true });
const visualization = findPageMapItem(docsPageMap, 'visualization');

const tutorialChildren = tutorial.children!;
const examples = findPageMapItem(tutorialChildren, 'basic', { children: true });
const advanced = findPageMapItem(tutorialChildren, 'advanced', {
children: true,
});
const gettingStarted = findPageMapItem(tutorialChildren, 'getting-started');
const usage = findPageMapItem(tutorialChildren, 'usage');

const exampleChildren = [
findPageMapItem(examples.children!, 'state'),
findPageMapItem(examples.children!, 'action'),
findPageMapItem(examples.children!, 'hybrid'),
];

return [
{
...withTitle(tutorial, 'Tutorial'),
children: [
withTitle(gettingStarted, 'Getting Started'),
withTitle(usage, 'Usage'),
{
...withTitle(examples, 'Examples'),
theme: {
...examples.theme,
collapsed: false,
},
children: exampleChildren,
},
{
...withTitle(advanced, 'Advanced'),
theme: {
...advanced.theme,
collapsed: false,
},
},
],
},
withTitle(visualization, 'Visualization'),
];
}

export default async function Page(props: {
params: Promise<PageParams>;
}) {
const params = await props.params;
const section = getContentSection(params.mdxPath);
const pageMapRoute = getPageMapRoute(section);
const { default: MDXContent, toc, metadata, sourceCode } = await importPage(
params.mdxPath
);

if (section === 'top-level') {
return (
<Layout
banner={banner}
navbar={navbar}
search={search}
pageMap={await getPageMap('/')}
docsRepositoryBase={docsRepositoryBase}
footer={footer}
>
<div className="x:mx-auto x:flex x:max-w-(--nextra-content-width)">
<article className={getArticleClass(params.mdxPath)}>
<SkipNavContent />
<main data-pagefind-body>
<MDXContent {...props} params={params} />
</main>
</article>
</div>
</Layout>
);
}

if (section === 'docs') {
return (
<Layout
banner={banner}
navbar={navbar}
search={search}
pageMap={await getDocsSidebarPageMap()}
docsRepositoryBase={docsRepositoryBase}
footer={footer}
>
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
<MDXContent {...props} params={params} />
</Wrapper>
</Layout>
);
}

return (
<Layout
banner={banner}
navbar={navbar}
search={search}
pageMap={await getPageMap(pageMapRoute)}
docsRepositoryBase={docsRepositoryBase}
footer={footer}
>
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
<MDXContent {...props} params={params} />
</Wrapper>
</Layout>
);
}
19 changes: 19 additions & 0 deletions apps/docs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata } from 'next';
import type { ReactNode } from 'react';
import '../styles/globals.css';
import { head, siteMetadata } from '../theme.config';

export const metadata: Metadata = siteMetadata;

export default async function RootLayout({
children,
}: {
children: ReactNode;
}) {
return (
<html lang="en" dir="ltr" suppressHydrationWarning>
{head}
<body>{children}</body>
</html>
);
}
63 changes: 63 additions & 0 deletions apps/docs/components/HeaderNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';

import clsx from 'clsx';
import { Anchor } from 'nextra/components';
import { useFSRoute } from 'nextra/hooks';

const NAV_ITEMS = [
{
label: 'Documentation',
href: '/docs/tutorial/getting-started',
activePrefixes: ['/docs'],
},
{
label: 'Showcase',
href: '/showcase',
activePrefixes: ['/showcase'],
},
{
label: 'API Reference',
href: '/api-reference',
activePrefixes: ['/api-reference'],
},
{
label: 'About',
href: '/about',
activePrefixes: ['/about'],
},
] as const;

function isCurrentRoute(route: string, activePrefixes: readonly string[]) {
return activePrefixes.some(
(prefix) => route === prefix || route.startsWith(`${prefix}/`),
);
}

export function HeaderNav() {
const route = useFSRoute().split('#', 1)[0];

return (
<div className="x:flex x:gap-4 x:overflow-x-auto nextra-scrollbar x:py-1.5">
{NAV_ITEMS.map(({ label, href, activePrefixes }) => {
const isActive = isCurrentRoute(route, activePrefixes);

return (
<Anchor
key={href}
href={href}
aria-current={isActive ? 'page' : undefined}
className={clsx(
'x:text-sm x:whitespace-nowrap x:ring-inset x:transition-colors',
'x:text-gray-600 x:hover:text-black',
'x:dark:text-gray-400 x:dark:hover:text-gray-200',
'x:contrast-more:text-gray-700 x:contrast-more:dark:text-gray-100',
isActive && 'x:font-medium x:subpixel-antialiased x:text-current',
)}
>
{label}
</Anchor>
);
})}
</div>
);
}
Loading
Loading