Skip to content

Commit

Permalink
[website] Modernize the Base UI website (#538)
Browse files Browse the repository at this point in the history
Co-authored-by: colmtuite <[email protected]>
  • Loading branch information
michaldudak and colmtuite authored Sep 5, 2024
1 parent 30245d0 commit 053b139
Show file tree
Hide file tree
Showing 1,052 changed files with 10,714 additions and 11,413 deletions.
6 changes: 0 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,6 @@ jobs:
- run:
name: '`pnpm docs:api` changes committed?'
command: git add -A && git diff --exit-code --staged
- run:
name: Update the navigation translations
command: pnpm docs:i18n
- run:
name: '`pnpm docs:i18n` changes committed?'
command: git add -A && git diff --exit-code --staged
- run:
name: '`pnpm extract-error-codes` changes committed?'
command: |
Expand Down
52 changes: 36 additions & 16 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ const OneLevelImportMessage = [
'See https://github.com/mui/material-ui/pull/24147 for the kind of win it can unlock.',
].join('\n');

const NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED = [
{
group: ['@base_ui/react/*/*', '!@base_ui/react/legacy/*'],
message: OneLevelImportMessage,
},
];

module.exports = {
...baseline,
settings: {
Expand All @@ -27,27 +34,15 @@ module.exports = {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: [
'@mui/*/*/*',
'@pigment-css/*/*/*',
'@base_ui/react/*/*',
'!@base_ui/react/legacy/*',
// Allow any import depth with any internal packages
'!@mui/internal-*/**',
// TODO delete, @mui/docs should be @mui/internal-docs
'!@mui/docs/**',
],
message: OneLevelImportMessage,
},
],
patterns: NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED,
},
],
'@typescript-eslint/no-redeclare': 'off',
},
overrides: [
...baseline.overrides,
...baseline.overrides.filter(
(ruleSet) => !ruleSet.rules.hasOwnProperty('filenames/match-exported'),
),
{
files: ['docs/pages/experiments/**/*{.tsx,.js}', 'docs/pages/playground/**/*{.tsx,.js}'],
rules: {
Expand All @@ -67,5 +62,30 @@ module.exports = {
'testing-library/render-result-naming-convention': 'off', // False positives
},
},
{
files: ['docs/**/*{.ts,.tsx,.js}'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED,
},
],
'react/prop-types': 'off',
'@typescript-eslint/no-use-before-define': 'off',
},
},
{
files: ['docs/data/**/*{.tsx,.js}'],
excludedFiles: [
'docs/data/**/css/*{.tsx,.js}',
'docs/data/**/css-modules/*{.tsx,.js}',
'docs/data/**/system/*{.tsx,.js}',
'docs/data/**/tailwind/*{.tsx,.js}',
],
rules: {
'filenames/match-exported': ['error'],
},
},
],
};
5 changes: 3 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"unifiedjs.vscode-mdx",
"yoavbls.pretty-ts-errors"
]
}
2 changes: 0 additions & 2 deletions docs/.link-check-errors.txt
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
Broken links found by `pnpm docs:link-check` that exist:

27 changes: 27 additions & 0 deletions docs/app/(content)/components/[slug]/getApiReferenceData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { readFile } from 'node:fs/promises';
import { ComponentAPIReference } from 'docs-base/types/ComponentAPIReference';
import kebabCase from 'lodash/kebabCase';

export function getApiReferenceData(componentNames: string[]): Promise<ComponentAPIReference[]> {
return Promise.all(
componentNames.map(async (componentName) => {
const kebabedComponentName = kebabCase(componentName);
const apiDescriptionFilePath = `data/api/${kebabedComponentName}.json`;
const translationsFilePath = `data/translations/api-docs/${kebabedComponentName}/${kebabedComponentName}.json`;

const apiDescription = JSON.parse(await readFile(apiDescriptionFilePath, 'utf-8'));
const translations = JSON.parse(await readFile(translationsFilePath, 'utf-8'));

return {
name: componentName,
description: translations.componentDescription,
props: Object.keys(apiDescription.props).map((propName) => ({
name: propName,
...apiDescription.props[propName],
defaultValue: apiDescription.props[propName].default ?? null,
...translations.propDescriptions[propName],
})),
} satisfies ComponentAPIReference;
}),
);
}
107 changes: 107 additions & 0 deletions docs/app/(content)/components/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as React from 'react';
import { Metadata } from 'next';
import { components } from 'docs-base/src/components/content/MDXComponents';
import { getMarkdownPage, getMarkdownPageMetadata } from 'docs-base/app/(content)/getMarkdownPage';
import { ComponentLinkHeader } from 'docs-base/src/components/content/ComponentLinkHeader';
import { Description } from 'docs-base/src/components/content/Description';
import { TableOfContents } from 'docs-base/src/components/TableOfContents';
import routes, { getSlugs } from 'docs-base/data/pages';
import { SiblingPageLinks } from 'docs-base/src/components/SiblingPageLinks';
import { EditPageGithubLink } from 'docs-base/src/components/EditPageGithhubLink';
import {
ApiReference,
getApiReferenceTableOfContents,
} from 'docs-base/src/components/ApiReference';
import { DemoLoader, DemoLoaderProps } from 'docs-base/src/components/demo/DemoLoader';
import { getApiReferenceData } from './getApiReferenceData';
import classes from '../../styles.module.css';

const CATEGORY_SEGMENT = 'components';

interface Props {
params: {
slug: string;
};
}

function componentNameFromSlug(slug: string) {
return slug.replace('react-', '');
}

export default async function ComponentPage(props: Props) {
const {
params: { slug },
} = props;

const componentName = componentNameFromSlug(slug);

const { MDXContent, metadata, tableOfContents } = await getMarkdownPage(
CATEGORY_SEGMENT,
componentName,
);

const documentedComponents = metadata.components?.split(',').map((c: string) => c.trim()) ?? [];
const componentsApi = await getApiReferenceData(documentedComponents);
const apiReferenceToc = getApiReferenceTableOfContents(componentsApi);

tableOfContents[0].children?.push(apiReferenceToc);

const allComponents = {
...components,
// eslint-disable-next-line react/no-unstable-nested-components
Demo: (demoProps: Omit<DemoLoaderProps, 'componentName'>) => (
<DemoLoader componentName={componentName} {...demoProps} />
),
// eslint-disable-next-line react/no-unstable-nested-components
Description: () => <Description text={metadata.description} />,
// eslint-disable-next-line react/no-unstable-nested-components
ComponentLinkHeader: () => (
<ComponentLinkHeader ariaSpecUrl={metadata.waiAria} githubLabel={metadata.githubLabel} />
),
};

return (
<React.Fragment>
<main className={classes.content}>
<MDXContent components={{ ...allComponents }} />
<ApiReference componentsApi={componentsApi} />
<div>
<div className={classes.editLink}>
<EditPageGithubLink category={CATEGORY_SEGMENT} slug={componentName} />
</div>
<div>
<SiblingPageLinks currentSlug={`/${CATEGORY_SEGMENT}/${slug}`} pages={routes} />
</div>
</div>
</main>

<TableOfContents toc={tableOfContents} renderDepth={3} />
</React.Fragment>
);
}

export async function generateStaticParams() {
return getSlugs(`/${CATEGORY_SEGMENT}`).map((slug) => ({ slug }));
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = params;
const componentName = componentNameFromSlug(slug);
const { title = 'Components', description } = await getMarkdownPageMetadata(
CATEGORY_SEGMENT,
componentName,
);

return {
title,
description,
twitter: {
title,
description,
},
openGraph: {
title,
description,
},
};
}
88 changes: 88 additions & 0 deletions docs/app/(content)/getMarkdownPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import path from 'node:path';
import fs from 'node:fs';
import { readFile } from 'node:fs/promises';
import rehypePrettyCode from 'rehype-pretty-code';
import { evaluate } from '@mdx-js/mdx';
import * as jsxRuntime from 'react/jsx-runtime';
import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';
import extractToc, { type Toc } from '@stefanprobst/rehype-extract-toc';
import exportToc from '@stefanprobst/rehype-extract-toc/mdx';
import { read as readVFile } from 'to-vfile';
import { matter } from 'vfile-matter';
import { config } from 'docs-base/config';

export const DATA_PATH = path.join(process.cwd(), 'data');

export interface PageMetadata {
title: string;
description: string;
components?: string;
githubLabel?: string;
waiAria?: string;
slug: string;
}

export const getMarkdownPage = async (basePath: string, slug: string) => {
const mdxFilePath = path.join(DATA_PATH, basePath, `/${slug}/${slug}.mdx`);
const mdFilePath = path.join(DATA_PATH, basePath, `/${slug}/${slug}.md`);

let filePath: string;

if (fs.existsSync(mdxFilePath)) {
filePath = mdxFilePath;
} else if (fs.existsSync(mdFilePath)) {
filePath = mdFilePath;
} else {
throw new Error(`No MD(X) file found for ${basePath}/${slug}`);
}

const mdxSource = await readFile(filePath, 'utf8');

const {
default: MDXContent,
frontmatter,
tableOfContents,
// @ts-ignore https://github.com/mdx-js/mdx/issues/2463
} = await evaluate(mdxSource, {
...jsxRuntime,
remarkPlugins: [remarkGfm, remarkFrontmatter, remarkMdxFrontmatter],
rehypePlugins: [
[rehypePrettyCode, { theme: config.shikiThemes }],
rehypeSlug,
extractToc,
exportToc,
],
});

return {
metadata: {
...(frontmatter as Partial<PageMetadata>),
slug,
} as PageMetadata,
tableOfContents: tableOfContents as Toc,
MDXContent,
};
};

export const getMarkdownPageMetadata = async (basePath: string, slug: string) => {
const mdxFilePath = path.join(DATA_PATH, basePath, `/${slug}/${slug}.mdx`);
const mdFilePath = path.join(DATA_PATH, basePath, `/${slug}/${slug}.md`);

let filePath: string;

if (fs.existsSync(mdxFilePath)) {
filePath = mdxFilePath;
} else if (fs.existsSync(mdFilePath)) {
filePath = mdFilePath;
} else {
throw new Error(`No MD(X) file found for ${basePath}/${slug}`);
}

const file = await readVFile(filePath);
matter(file);

return file.data.matter as PageMetadata;
};
Loading

0 comments on commit 053b139

Please sign in to comment.