From 61a161ce68252e7fa2c783dd6e7f8d2a9af035c3 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:09:43 +0200 Subject: [PATCH 01/40] WIP --- docs/package.json | 3 +- docs/scripts/generateLlmTxt.mjs | 88 +++++++++++ docs/scripts/mdxToMarkdown.mjs | 270 ++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 361 insertions(+), 1 deletion(-) create mode 100755 docs/scripts/generateLlmTxt.mjs create mode 100644 docs/scripts/mdxToMarkdown.mjs diff --git a/docs/package.json b/docs/package.json index c321d7279c..10615b09a6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,7 +11,8 @@ "deploy": "git push -f upstream master:docs-v1", "serve": "serve ./export -l 3010", "typescript": "tsc -b tsconfig.json", - "link-check": "tsx ./scripts/reportBrokenLinks.mts" + "link-check": "tsx ./scripts/reportBrokenLinks.mts", + "generate-llms": "node ./scripts/generateLlmTxt.mjs" }, "dependencies": { "@babel/core": "^7.26.9", diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs new file mode 100755 index 0000000000..0b938bdf71 --- /dev/null +++ b/docs/scripts/generateLlmTxt.mjs @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +/** + * generateLlmTxt.mjs - Generates llms.txt and markdown files from MDX content + * + * This script performs the following: + * 1. Scans all MDX files in the docs/src/app/(public)/(content)/react folder + * 2. Converts each MDX file to markdown using a custom React reconciler + * 3. Outputs the files to docs/llms directory + * 4. Creates llms.txt according to https://llmstxt.org/ format + */ + +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import glob from 'fast-glob'; +import { mdxToMarkdown } from './mdxToMarkdown.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = path.resolve(__dirname, '..'); +const MDX_SOURCE_DIR = path.join(PROJECT_ROOT, 'src/app/(public)/(content)/react'); +const OUTPUT_DIR = path.join(PROJECT_ROOT, 'llms'); + +/** + * Generate llms.txt and markdown files from MDX content + */ +async function generateLlmsTxt() { + console.log('Generating llms.txt and markdown files...'); + + try { + // Create output directory if it doesn't exist + await fs.mkdir(OUTPUT_DIR, { recursive: true }); + + // Find all MDX files + const mdxFiles = await glob('**/*/page.mdx', { + cwd: MDX_SOURCE_DIR, + absolute: true + }); + + console.log(`Found ${mdxFiles.length} MDX files`); + + // Generate llms.txt entries + const llmsEntries = []; + + // Process each MDX file + for (const mdxFile of mdxFiles) { + // Get relative path for URL generation + const relativePath = path.relative(MDX_SOURCE_DIR, mdxFile); + const dirPath = path.dirname(relativePath); + + // Create URL for llms.txt (without /page.mdx) + const urlPath = dirPath.replace(/\\/g, '/'); + const url = `https://base-ui.org/react/${urlPath}`; + + // Read MDX content + const mdxContent = await fs.readFile(mdxFile, 'utf-8'); + + // Convert to markdown + const markdown = await mdxToMarkdown(mdxContent); + + // Get output file path + const outputFilePath = path.join(OUTPUT_DIR, `${dirPath.replace(/\\/g, '-')}.md`); + + // Create directories for output if needed + await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); + + // Write markdown file + await fs.writeFile(outputFilePath, markdown, 'utf-8'); + + // Add entry to llms.txt + llmsEntries.push(`${url} ${outputFilePath}`); + + console.log(`Processed: ${relativePath}`); + } + + // Create llms.txt + const llmsTxtContent = llmsEntries.join('\n'); + await fs.writeFile(path.join(OUTPUT_DIR, 'llms.txt'), llmsTxtContent, 'utf-8'); + + console.log(`Successfully generated ${mdxFiles.length} markdown files and llms.txt`); + } catch (error) { + console.error('Error generating llms.txt:', error); + process.exit(1); + } +} + +// Run the generator +generateLlmsTxt(); \ No newline at end of file diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs new file mode 100644 index 0000000000..89bccbb930 --- /dev/null +++ b/docs/scripts/mdxToMarkdown.mjs @@ -0,0 +1,270 @@ +/** + * mdxToMarkdown.mjs - Converts MDX content to Markdown + * + * This module provides functions to transform MDX content to Markdown format + * using a custom React-like reconciler. + */ + +import { evaluate } from '@mdx-js/mdx'; +import * as React from 'react'; +import * as jsxRuntime from 'react/jsx-runtime'; + +/** + * Creates a Markdown renderer that converts React elements to Markdown + * @returns {Object} A renderer with a render method + */ +function createMarkdownRenderer() { + // Track the current heading level + let headingLevel = 0; + + // Track whether we're in a list + let inList = false; + let listType = null; + let listItemNumber = 0; + + // Track table state + let inTable = false; + let tableHeader = []; + let tableRows = []; + let currentRow = []; + + // Keep track of parent elements + const elementStack = []; + + const renderChildren = (children) => { + if (!children) return ''; + + if (typeof children === 'string') { + return children; + } + + if (Array.isArray(children)) { + return children.map((child) => renderElement(child)).join(''); + } + + return renderElement(children); + }; + + const renderElement = (element) => { + if (element == null) return ''; + + // Handle primitive values + if (typeof element === 'string') return element; + if (typeof element === 'number') return String(element); + if (typeof element === 'boolean') return ''; + + // Skip if not a valid React element + if (!element.type) return ''; + + // Extract props and children + const { type, props } = element; + const { children, ...otherProps } = props || {}; + + // Track element hierarchy for parent-child relationships + elementStack.push(element); + + try { + // Handle different element types + if (typeof type === 'string') { + let result; + switch (type) { + case 'h1': + return `# ${renderChildren(children)}\n\n`; + case 'h2': + return `## ${renderChildren(children)}\n\n`; + case 'h3': + return `### ${renderChildren(children)}\n\n`; + case 'h4': + return `#### ${renderChildren(children)}\n\n`; + case 'h5': + return `##### ${renderChildren(children)}\n\n`; + case 'h6': + return `###### ${renderChildren(children)}\n\n`; + case 'p': + return `${renderChildren(children)}\n\n`; + case 'a': + return `[${renderChildren(children)}](${otherProps.href || '#'})`; + case 'strong': + case 'b': + return `**${renderChildren(children)}**`; + case 'em': + case 'i': + return `*${renderChildren(children)}*`; + case 'code': { + // Check if parent is a 'pre' element to determine if this is a code block + const parentElement = + elementStack.length > 1 ? elementStack[elementStack.length - 2] : null; + const isCodeBlock = parentElement && parentElement.type === 'pre'; + + if (isCodeBlock) { + // This is part of a code block, just return content + return renderChildren(children); + } else { + // This is inline code + return `\`${renderChildren(children)}\``; + } + } + case 'pre': { + const codeContent = renderChildren(children); + return `\`\`\`\n${codeContent}\n\`\`\`\n\n`; + } + case 'ul': + inList = true; + listType = 'unordered'; + const ulResult = renderChildren(children); + inList = false; + listType = null; + return `${ulResult}\n`; + case 'ol': + inList = true; + listType = 'ordered'; + listItemNumber = 0; + const olResult = renderChildren(children); + inList = false; + listType = null; + return `${olResult}\n`; + case 'li': + if (listType === 'unordered') { + return `- ${renderChildren(children)}\n`; + } else if (listType === 'ordered') { + listItemNumber++; + return `${listItemNumber}. ${renderChildren(children)}\n`; + } + return `- ${renderChildren(children)}\n`; + case 'table': + inTable = true; + tableHeader = []; + tableRows = []; + renderChildren(children); + inTable = false; + + // Build the markdown table + if (tableHeader.length === 0) return ''; + + // Create header and separator + const headerRow = `| ${tableHeader.join(' | ')} |`; + const separator = `| ${tableHeader.map(() => '---').join(' | ')} |`; + + // Create data rows + const dataRows = tableRows + .map((row) => { + // Fill missing cells with empty strings + while (row.length < tableHeader.length) { + row.push(''); + } + return `| ${row.join(' | ')} |`; + }) + .join('\n'); + + result = `${headerRow}\n${separator}\n${dataRows}\n\n`; + return result; + case 'thead': + case 'tbody': + case 'tfoot': + return renderChildren(children); + case 'tr': + if (inTable) { + if (tableHeader.length === 0) { + // This is a header row + currentRow = []; + renderChildren(children); + tableHeader = [...currentRow]; + } else { + // This is a data row + currentRow = []; + renderChildren(children); + tableRows.push([...currentRow]); + } + } + return ''; + case 'th': + case 'td': + if (inTable) { + currentRow.push(renderChildren(children)); + } + return ''; + case 'blockquote': + // Process each line with '>' prefix + const rawContent = renderChildren(children); + const quotedContent = rawContent + .split('\n') + .map((line) => (line ? `> ${line}` : '>')) + .join('\n'); + return `${quotedContent}\n\n`; + case 'hr': + return `---\n\n`; + case 'br': + return `\n`; + default: + // For custom or unknown components, just render their children + return renderChildren(children); + } + } + + // Handle React fragments + if ( + type === React.Fragment || + (jsxRuntime && jsxRuntime.Fragment && type === jsxRuntime.Fragment) + ) { + return renderChildren(children); + } + + // Handle function components by calling them with props + if (typeof type === 'function') { + const renderedResult = type({ ...otherProps, children }); + return renderElement(renderedResult); + } + + return ''; + } finally { + // Always pop the stack, even if an error occurs + elementStack.pop(); + } + }; + + return { + render: (element) => { + return renderElement(element); + }, + }; +} + +/** + * Converts MDX content to markdown + * @param {string} mdxContent - The MDX content to convert + * @returns {Promise} The converted markdown + */ +export async function mdxToMarkdown(mdxContent) { + try { + // Evaluate MDX to get React component + const { default: MDXComponent } = await evaluate(mdxContent, { + ...jsxRuntime, + development: false, + }); + + // Create simple props for MDX component + const props = { + components: { + Meta: () => '--- Meta ---', + Demo: () => '--- Demo ---', + Subtitle: () => '--- Subtitle ---', + Reference: () => '--- Reference ---', + PropsReferenceTable: () => '--- PropsReferenceTable ---', + }, + }; + + // Create a markdown renderer + const markdownRenderer = createMarkdownRenderer(); + + // Create a React element from the MDX component + const element = React.createElement(MDXComponent, props); + + // Render to markdown + const markdown = markdownRenderer.render(element); + + return markdown; + } catch (error) { + console.error('Error converting MDX to Markdown:', error); + return `Error converting MDX to Markdown: ${error.message}`; + } +} diff --git a/package.json b/package.json index 63116fa90b..99da4079c2 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "docs:size-why": "cross-env DOCS_STATS_ENABLED=true pnpm docs:build", "docs:start": "pnpm --filter docs serve", "docs:link-check": "pnpm --filter docs link-check", + "docs:generate-llms": "pnpm --filter docs run generate-llms", "extract-error-codes": "cross-env MUI_EXTRACT_ERROR_CODES=true lerna run --concurrency 8 build:modern", "install:codesandbox": "pnpm install --no-frozen-lockfile", "jsonlint": "node ./scripts/jsonlint.mjs", From 308dfc3ffdfc76e8fdeef0f06da76e8ba0d73e57 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:21:04 +0200 Subject: [PATCH 02/40] WIP --- docs/scripts/generateLlmTxt.mjs | 17 ++++- docs/scripts/mdxToMarkdown.mjs | 119 +++++++++++++++++++++----------- 2 files changed, 94 insertions(+), 42 deletions(-) diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 0b938bdf71..7ad7ca76a2 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -55,8 +55,8 @@ async function generateLlmsTxt() { // Read MDX content const mdxContent = await fs.readFile(mdxFile, 'utf-8'); - // Convert to markdown - const markdown = await mdxToMarkdown(mdxContent); + // Convert to markdown and extract metadata + const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent); // Get output file path const outputFilePath = path.join(OUTPUT_DIR, `${dirPath.replace(/\\/g, '-')}.md`); @@ -64,8 +64,19 @@ async function generateLlmsTxt() { // Create directories for output if needed await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); + // Create markdown content with frontmatter + const frontmatter = [ + '---', + `title: ${title || 'Untitled'}`, + subtitle ? `subtitle: ${subtitle}` : '', + description ? `description: ${description}` : '', + '---', + '', + markdown + ].filter(Boolean).join('\n'); + // Write markdown file - await fs.writeFile(outputFilePath, markdown, 'utf-8'); + await fs.writeFile(outputFilePath, frontmatter, 'utf-8'); // Add entry to llms.txt llmsEntries.push(`${url} ${outputFilePath}`); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 89bccbb930..0046ec610d 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -1,6 +1,6 @@ /** * mdxToMarkdown.mjs - Converts MDX content to Markdown - * + * * This module provides functions to transform MDX content to Markdown format * using a custom React-like reconciler. */ @@ -16,35 +16,35 @@ import * as jsxRuntime from 'react/jsx-runtime'; function createMarkdownRenderer() { // Track the current heading level let headingLevel = 0; - + // Track whether we're in a list let inList = false; let listType = null; let listItemNumber = 0; - + // Track table state let inTable = false; let tableHeader = []; let tableRows = []; let currentRow = []; - + // Keep track of parent elements const elementStack = []; - + const renderChildren = (children) => { if (!children) return ''; - + if (typeof children === 'string') { return children; } - + if (Array.isArray(children)) { - return children.map((child) => renderElement(child)).join(''); + return children.map(child => renderElement(child)).join(''); } - + return renderElement(children); }; - + const renderElement = (element) => { if (element == null) return ''; @@ -62,7 +62,7 @@ function createMarkdownRenderer() { // Track element hierarchy for parent-child relationships elementStack.push(element); - + try { // Handle different element types if (typeof type === 'string') { @@ -92,10 +92,9 @@ function createMarkdownRenderer() { return `*${renderChildren(children)}*`; case 'code': { // Check if parent is a 'pre' element to determine if this is a code block - const parentElement = - elementStack.length > 1 ? elementStack[elementStack.length - 2] : null; + const parentElement = elementStack.length > 1 ? elementStack[elementStack.length - 2] : null; const isCodeBlock = parentElement && parentElement.type === 'pre'; - + if (isCodeBlock) { // This is part of a code block, just return content return renderChildren(children); @@ -106,7 +105,14 @@ function createMarkdownRenderer() { } case 'pre': { const codeContent = renderChildren(children); - return `\`\`\`\n${codeContent}\n\`\`\`\n\n`; + + // Extract language from data-language attribute if available + let language = ''; + if (otherProps['data-language']) { + language = otherProps['data-language']; + } + + return `\`\`\`${language}\n${codeContent}\n\`\`\`\n\n`; } case 'ul': inList = true; @@ -137,17 +143,17 @@ function createMarkdownRenderer() { tableRows = []; renderChildren(children); inTable = false; - + // Build the markdown table if (tableHeader.length === 0) return ''; - + // Create header and separator const headerRow = `| ${tableHeader.join(' | ')} |`; const separator = `| ${tableHeader.map(() => '---').join(' | ')} |`; - + // Create data rows const dataRows = tableRows - .map((row) => { + .map(row => { // Fill missing cells with empty strings while (row.length < tableHeader.length) { row.push(''); @@ -155,7 +161,7 @@ function createMarkdownRenderer() { return `| ${row.join(' | ')} |`; }) .join('\n'); - + result = `${headerRow}\n${separator}\n${dataRows}\n\n`; return result; case 'thead': @@ -188,7 +194,7 @@ function createMarkdownRenderer() { const rawContent = renderChildren(children); const quotedContent = rawContent .split('\n') - .map((line) => (line ? `> ${line}` : '>')) + .map(line => line ? `> ${line}` : '>') .join('\n'); return `${quotedContent}\n\n`; case 'hr': @@ -202,13 +208,10 @@ function createMarkdownRenderer() { } // Handle React fragments - if ( - type === React.Fragment || - (jsxRuntime && jsxRuntime.Fragment && type === jsxRuntime.Fragment) - ) { + if (type === React.Fragment || (jsxRuntime && jsxRuntime.Fragment && type === jsxRuntime.Fragment)) { return renderChildren(children); } - + // Handle function components by calling them with props if (typeof type === 'function') { const renderedResult = type({ ...otherProps, children }); @@ -225,46 +228,84 @@ function createMarkdownRenderer() { return { render: (element) => { return renderElement(element); - }, + } }; } /** - * Converts MDX content to markdown + * Converts MDX content to markdown and extracts metadata * @param {string} mdxContent - The MDX content to convert - * @returns {Promise} The converted markdown + * @returns {Promise} An object containing the markdown and metadata */ export async function mdxToMarkdown(mdxContent) { try { + // Store extracted metadata + let title = ''; + let subtitle = ''; + let description = ''; + // Evaluate MDX to get React component const { default: MDXComponent } = await evaluate(mdxContent, { ...jsxRuntime, development: false, }); - // Create simple props for MDX component + // Create props for MDX component with metadata extraction const props = { components: { - Meta: () => '--- Meta ---', + Meta: (props) => { + // Extract description from Meta component + if (props.name === 'description') { + description = props.content; + // Render description as a paragraph + return React.createElement('p', {}, props.content); + } + return null; + }, Demo: () => '--- Demo ---', - Subtitle: () => '--- Subtitle ---', + Subtitle: (props) => { + // Extract subtitle from Subtitle component + subtitle = props.children; + // Render subtitle as italic paragraph + return React.createElement('p', {}, + React.createElement('em', {}, props.children) + ); + }, Reference: () => '--- Reference ---', PropsReferenceTable: () => '--- PropsReferenceTable ---', + h1: (props) => { + // Extract title from h1 element + if (typeof props.children === 'string') { + title = props.children; + } + return React.createElement('h1', props); + } }, }; - + // Create a markdown renderer const markdownRenderer = createMarkdownRenderer(); - + // Create a React element from the MDX component const element = React.createElement(MDXComponent, props); - + // Render to markdown const markdown = markdownRenderer.render(element); - - return markdown; + + // Return markdown and metadata + return { + markdown, + title, + subtitle, + description + }; } catch (error) { console.error('Error converting MDX to Markdown:', error); - return `Error converting MDX to Markdown: ${error.message}`; + return { + markdown: `Error converting MDX to Markdown: ${error.message}`, + title: '', + subtitle: '', + description: '' + }; } -} +} \ No newline at end of file From c94facd1c15f1f38cebcb820df24ef34f453962b Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:50:57 +0200 Subject: [PATCH 03/40] WIP --- docs/package.json | 8 +- docs/scripts/generateLlmTxt.mjs | 46 ++-- docs/scripts/mdxToMarkdown.mjs | 383 ++++++++++---------------------- package.json | 1 + pnpm-lock.yaml | 115 ++++++---- 5 files changed, 221 insertions(+), 332 deletions(-) diff --git a/docs/package.json b/docs/package.json index 10615b09a6..0d5a46d8ec 100644 --- a/docs/package.json +++ b/docs/package.json @@ -24,7 +24,6 @@ "@emotion/server": "^11.11.0", "@emotion/styled": "^11.14.0", "@mdx-js/loader": "^3.1.0", - "@mdx-js/mdx": "^3.1.0", "@mdx-js/react": "^3.1.0", "@mui/system": "6.4.8", "@next/mdx": "^15.2.1", @@ -62,19 +61,18 @@ "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-mdx-frontmatter": "^5.0.0", - "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-typography": "0.6.21", "scroll-into-view-if-needed": "3.1.0", "shiki": "^1.29.2", "to-vfile": "^8.0.0", - "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile-matter": "^5.0.0" }, "devDependencies": { "@babel/plugin-transform-react-constant-elements": "^7.25.9", "@babel/preset-typescript": "^7.26.0", + "@mdx-js/mdx": "^3.1.0", "@mui/internal-docs-utils": "^1.0.16", "@mui/internal-scripts": "^1.0.33", "@mui/internal-test-utils": "^1.0.27", @@ -92,10 +90,14 @@ "mdast-util-mdx-jsx": "^3.2.0", "motion": "^11.18.2", "prettier": "^3.4.2", + "react-reconciler": "^0.32.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", "rimraf": "^5.0.10", "serve": "^14.2.4", "tailwindcss": "4.0.3", "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", "webpack-bundle-analyzer": "^4.10.2", "yargs": "^17.7.2", "zod": "^3.24.2" diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 7ad7ca76a2..3b4a821420 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -2,7 +2,7 @@ /** * generateLlmTxt.mjs - Generates llms.txt and markdown files from MDX content - * + * * This script performs the following: * 1. Scans all MDX files in the docs/src/app/(public)/(content)/react folder * 2. Converts each MDX file to markdown using a custom React reconciler @@ -26,44 +26,44 @@ const OUTPUT_DIR = path.join(PROJECT_ROOT, 'llms'); */ async function generateLlmsTxt() { console.log('Generating llms.txt and markdown files...'); - + try { // Create output directory if it doesn't exist await fs.mkdir(OUTPUT_DIR, { recursive: true }); - + // Find all MDX files - const mdxFiles = await glob('**/*/page.mdx', { + const mdxFiles = await glob('**/*/page.mdx', { cwd: MDX_SOURCE_DIR, - absolute: true + absolute: true, }); - + console.log(`Found ${mdxFiles.length} MDX files`); - + // Generate llms.txt entries const llmsEntries = []; - + // Process each MDX file for (const mdxFile of mdxFiles) { // Get relative path for URL generation const relativePath = path.relative(MDX_SOURCE_DIR, mdxFile); const dirPath = path.dirname(relativePath); - + // Create URL for llms.txt (without /page.mdx) const urlPath = dirPath.replace(/\\/g, '/'); const url = `https://base-ui.org/react/${urlPath}`; - + // Read MDX content const mdxContent = await fs.readFile(mdxFile, 'utf-8'); - + // Convert to markdown and extract metadata const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent); - + // Get output file path const outputFilePath = path.join(OUTPUT_DIR, `${dirPath.replace(/\\/g, '-')}.md`); - + // Create directories for output if needed await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); - + // Create markdown content with frontmatter const frontmatter = [ '---', @@ -72,22 +72,24 @@ async function generateLlmsTxt() { description ? `description: ${description}` : '', '---', '', - markdown - ].filter(Boolean).join('\n'); - + markdown, + ] + .filter(Boolean) + .join('\n'); + // Write markdown file await fs.writeFile(outputFilePath, frontmatter, 'utf-8'); - + // Add entry to llms.txt llmsEntries.push(`${url} ${outputFilePath}`); - + console.log(`Processed: ${relativePath}`); } - + // Create llms.txt const llmsTxtContent = llmsEntries.join('\n'); await fs.writeFile(path.join(OUTPUT_DIR, 'llms.txt'), llmsTxtContent, 'utf-8'); - + console.log(`Successfully generated ${mdxFiles.length} markdown files and llms.txt`); } catch (error) { console.error('Error generating llms.txt:', error); @@ -96,4 +98,4 @@ async function generateLlmsTxt() { } // Run the generator -generateLlmsTxt(); \ No newline at end of file +generateLlmsTxt(); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 0046ec610d..d265c4589f 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -1,234 +1,118 @@ /** * mdxToMarkdown.mjs - Converts MDX content to Markdown - * - * This module provides functions to transform MDX content to Markdown format - * using a custom React-like reconciler. + * + * This module transforms MDX content to Markdown format + * using remark and remark-mdx plugin. */ -import { evaluate } from '@mdx-js/mdx'; -import * as React from 'react'; -import * as jsxRuntime from 'react/jsx-runtime'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkMdx from 'remark-mdx'; +import remarkStringify from 'remark-stringify'; +import { visit } from 'unist-util-visit'; /** - * Creates a Markdown renderer that converts React elements to Markdown - * @returns {Object} A renderer with a render method + * Plugin to extract metadata from the MDX content */ -function createMarkdownRenderer() { - // Track the current heading level - let headingLevel = 0; - - // Track whether we're in a list - let inList = false; - let listType = null; - let listItemNumber = 0; - - // Track table state - let inTable = false; - let tableHeader = []; - let tableRows = []; - let currentRow = []; - - // Keep track of parent elements - const elementStack = []; - - const renderChildren = (children) => { - if (!children) return ''; - - if (typeof children === 'string') { - return children; - } - - if (Array.isArray(children)) { - return children.map(child => renderElement(child)).join(''); - } - - return renderElement(children); - }; - - const renderElement = (element) => { - if (element == null) return ''; +function extractMetadata() { + return (tree, file) => { + // Initialize metadata in file.data + file.data.metadata = { + title: '', + subtitle: '', + description: '' + }; - // Handle primitive values - if (typeof element === 'string') return element; - if (typeof element === 'number') return String(element); - if (typeof element === 'boolean') return ''; + // Extract title from first h1 + visit(tree, 'heading', (node) => { + if (node.depth === 1 && node.children?.[0]?.value) { + file.data.metadata.title = node.children[0].value; + return; + } + }); - // Skip if not a valid React element - if (!element.type) return ''; + // Extract from MDX components + visit(tree, 'mdxJsxFlowElement', (node) => { + // Extract from Subtitle component + if (node.name === 'Subtitle' && node.children?.[0]?.value) { + file.data.metadata.subtitle = node.children[0].value; + } + // Extract from Meta component + else if (node.name === 'Meta') { + const nameAttr = node.attributes?.find(attr => + attr.name === 'name' && attr.value === 'description'); + const contentAttr = node.attributes?.find(attr => + attr.name === 'content'); + + if (nameAttr && contentAttr) { + file.data.metadata.description = contentAttr.value; + } + } + }); - // Extract props and children - const { type, props } = element; - const { children, ...otherProps } = props || {}; + return tree; + }; +} - // Track element hierarchy for parent-child relationships - elementStack.push(element); - - try { - // Handle different element types - if (typeof type === 'string') { - let result; - switch (type) { - case 'h1': - return `# ${renderChildren(children)}\n\n`; - case 'h2': - return `## ${renderChildren(children)}\n\n`; - case 'h3': - return `### ${renderChildren(children)}\n\n`; - case 'h4': - return `#### ${renderChildren(children)}\n\n`; - case 'h5': - return `##### ${renderChildren(children)}\n\n`; - case 'h6': - return `###### ${renderChildren(children)}\n\n`; - case 'p': - return `${renderChildren(children)}\n\n`; - case 'a': - return `[${renderChildren(children)}](${otherProps.href || '#'})`; - case 'strong': - case 'b': - return `**${renderChildren(children)}**`; - case 'em': - case 'i': - return `*${renderChildren(children)}*`; - case 'code': { - // Check if parent is a 'pre' element to determine if this is a code block - const parentElement = elementStack.length > 1 ? elementStack[elementStack.length - 2] : null; - const isCodeBlock = parentElement && parentElement.type === 'pre'; - - if (isCodeBlock) { - // This is part of a code block, just return content - return renderChildren(children); - } else { - // This is inline code - return `\`${renderChildren(children)}\``; - } - } - case 'pre': { - const codeContent = renderChildren(children); - - // Extract language from data-language attribute if available - let language = ''; - if (otherProps['data-language']) { - language = otherProps['data-language']; - } - - return `\`\`\`${language}\n${codeContent}\n\`\`\`\n\n`; +/** + * Plugin to remove or simplify MDX-specific syntax + */ +function simplifyMdx() { + return (tree) => { + // Handle special components by replacing them with markdown equivalents + visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { + if (!parent) return; + + switch (node.name) { + case 'Demo': + parent.children.splice(index, 1, { + type: 'paragraph', + children: [{ type: 'text', value: '--- Demo ---' }] + }); + return; + case 'Reference': + parent.children.splice(index, 1, { + type: 'paragraph', + children: [{ type: 'text', value: '--- Reference ---' }] + }); + return; + case 'PropsReferenceTable': + parent.children.splice(index, 1, { + type: 'paragraph', + children: [{ type: 'text', value: '--- PropsReferenceTable ---' }] + }); + return; + case 'Subtitle': + if (node.children?.[0]?.value) { + parent.children.splice(index, 1, { + type: 'paragraph', + children: [{ + type: 'emphasis', + children: [{ type: 'text', value: node.children[0].value }] + }] + }); } - case 'ul': - inList = true; - listType = 'unordered'; - const ulResult = renderChildren(children); - inList = false; - listType = null; - return `${ulResult}\n`; - case 'ol': - inList = true; - listType = 'ordered'; - listItemNumber = 0; - const olResult = renderChildren(children); - inList = false; - listType = null; - return `${olResult}\n`; - case 'li': - if (listType === 'unordered') { - return `- ${renderChildren(children)}\n`; - } else if (listType === 'ordered') { - listItemNumber++; - return `${listItemNumber}. ${renderChildren(children)}\n`; - } - return `- ${renderChildren(children)}\n`; - case 'table': - inTable = true; - tableHeader = []; - tableRows = []; - renderChildren(children); - inTable = false; - - // Build the markdown table - if (tableHeader.length === 0) return ''; - - // Create header and separator - const headerRow = `| ${tableHeader.join(' | ')} |`; - const separator = `| ${tableHeader.map(() => '---').join(' | ')} |`; - - // Create data rows - const dataRows = tableRows - .map(row => { - // Fill missing cells with empty strings - while (row.length < tableHeader.length) { - row.push(''); - } - return `| ${row.join(' | ')} |`; - }) - .join('\n'); - - result = `${headerRow}\n${separator}\n${dataRows}\n\n`; - return result; - case 'thead': - case 'tbody': - case 'tfoot': - return renderChildren(children); - case 'tr': - if (inTable) { - if (tableHeader.length === 0) { - // This is a header row - currentRow = []; - renderChildren(children); - tableHeader = [...currentRow]; - } else { - // This is a data row - currentRow = []; - renderChildren(children); - tableRows.push([...currentRow]); - } - } - return ''; - case 'th': - case 'td': - if (inTable) { - currentRow.push(renderChildren(children)); - } - return ''; - case 'blockquote': - // Process each line with '>' prefix - const rawContent = renderChildren(children); - const quotedContent = rawContent - .split('\n') - .map(line => line ? `> ${line}` : '>') - .join('\n'); - return `${quotedContent}\n\n`; - case 'hr': - return `---\n\n`; - case 'br': - return `\n`; - default: - // For custom or unknown components, just render their children - return renderChildren(children); - } + return; } + }); - // Handle React fragments - if (type === React.Fragment || (jsxRuntime && jsxRuntime.Fragment && type === jsxRuntime.Fragment)) { - return renderChildren(children); - } - - // Handle function components by calling them with props - if (typeof type === 'function') { - const renderedResult = type({ ...otherProps, children }); - return renderElement(renderedResult); + // Remove imports and exports + visit(tree, ['mdxjsEsm', 'mdxFlowExpression'], (node, index, parent) => { + if (parent) { + parent.children.splice(index, 1); + return [visit.SKIP, index]; } + }); - return ''; - } finally { - // Always pop the stack, even if an error occurs - elementStack.pop(); - } - }; + // Remove inline expressions + visit(tree, 'mdxTextExpression', (node, index, parent) => { + if (parent) { + parent.children.splice(index, 1); + return [visit.SKIP, index]; + } + }); - return { - render: (element) => { - return renderElement(element); - } + return tree; }; } @@ -239,60 +123,29 @@ function createMarkdownRenderer() { */ export async function mdxToMarkdown(mdxContent) { try { - // Store extracted metadata - let title = ''; - let subtitle = ''; - let description = ''; - - // Evaluate MDX to get React component - const { default: MDXComponent } = await evaluate(mdxContent, { - ...jsxRuntime, - development: false, - }); + // Process the MDX content + const file = await unified() + .use(remarkParse) + .use(remarkMdx) + .use(extractMetadata) + .use(simplifyMdx) + .use(remarkStringify, { + bullet: '-', + emphasis: '*', + strong: '*', + fence: '`', + fences: true, + listItemIndent: 'one', + rule: '-' + }) + .process(mdxContent); - // Create props for MDX component with metadata extraction - const props = { - components: { - Meta: (props) => { - // Extract description from Meta component - if (props.name === 'description') { - description = props.content; - // Render description as a paragraph - return React.createElement('p', {}, props.content); - } - return null; - }, - Demo: () => '--- Demo ---', - Subtitle: (props) => { - // Extract subtitle from Subtitle component - subtitle = props.children; - // Render subtitle as italic paragraph - return React.createElement('p', {}, - React.createElement('em', {}, props.children) - ); - }, - Reference: () => '--- Reference ---', - PropsReferenceTable: () => '--- PropsReferenceTable ---', - h1: (props) => { - // Extract title from h1 element - if (typeof props.children === 'string') { - title = props.children; - } - return React.createElement('h1', props); - } - }, - }; - - // Create a markdown renderer - const markdownRenderer = createMarkdownRenderer(); - - // Create a React element from the MDX component - const element = React.createElement(MDXComponent, props); + // Get markdown content as string + const markdown = String(file); - // Render to markdown - const markdown = markdownRenderer.render(element); + // Extract metadata from the file's data + const { title = '', subtitle = '', description = '' } = file.data.metadata || {}; - // Return markdown and metadata return { markdown, title, diff --git a/package.json b/package.json index 99da4079c2..8a79f98a65 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "react-dom": "^19.0.0", "recast": "^0.23.11", "remark": "^15.0.1", + "remark-mdx": "^3.1.0", "rimraf": "^5.0.10", "serve": "^14.2.4", "style-loader": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9568eadab..aac496a28c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 0.4.24 chai-dom: specifier: ^1.12.1 - version: 1.12.1(chai@5.2.0) + version: 1.12.1(chai@4.5.0) chalk: specifier: ^5.4.1 version: 5.4.1 @@ -263,6 +263,9 @@ importers: remark: specifier: ^15.0.1 version: 15.0.1 + remark-mdx: + specifier: ^3.1.0 + version: 3.1.0 rimraf: specifier: ^5.0.10 version: 5.0.10 @@ -286,7 +289,7 @@ importers: version: 5.37.0 terser-webpack-plugin: specifier: ^5.3.12 - version: 5.3.12(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))) + version: 5.3.12(webpack@5.97.1) tsc-alias: specifier: ^1.8.11 version: 1.8.11 @@ -310,7 +313,7 @@ importers: version: 3.0.7(@types/debug@4.1.12)(@types/node@18.19.79)(@vitest/browser@3.0.7)(@vitest/ui@3.0.7)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.29.1)(msw@2.7.3(@types/node@18.19.79)(typescript@5.8.2))(terser@5.37.0)(tsx@4.19.3) webpack: specifier: ^5.97.1 - version: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + version: 5.97.1(webpack-cli@5.1.4) webpack-bundle-analyzer: specifier: ^4.10.2 version: 4.10.2 @@ -349,10 +352,7 @@ importers: version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) '@mdx-js/loader': specifier: ^3.1.0 - version: 3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))) - '@mdx-js/mdx': - specifier: ^3.1.0 - version: 3.1.0(acorn@8.14.1) + version: 3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4)) '@mdx-js/react': specifier: ^3.1.0 version: 3.1.0(@types/react@19.0.10)(react@19.0.0) @@ -361,7 +361,7 @@ importers: version: 6.4.8(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0) '@next/mdx': specifier: ^15.2.1 - version: 15.2.1(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0)) + version: 15.2.1(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4)))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0)) '@react-spring/web': specifier: ^9.7.5 version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -464,9 +464,6 @@ importers: remark-mdx-frontmatter: specifier: ^5.0.0 version: 5.0.0 - remark-parse: - specifier: ^11.0.0 - version: 11.0.0 remark-rehype: specifier: ^11.1.1 version: 11.1.1 @@ -482,9 +479,6 @@ importers: to-vfile: specifier: ^8.0.0 version: 8.0.0 - unist-util-visit: - specifier: ^5.0.0 - version: 5.0.0 unist-util-visit-parents: specifier: ^6.0.1 version: 6.0.1 @@ -498,6 +492,9 @@ importers: '@babel/preset-typescript': specifier: ^7.26.0 version: 7.26.0(@babel/core@7.26.9) + '@mdx-js/mdx': + specifier: ^3.1.0 + version: 3.1.0(acorn@8.14.1) '@mui/internal-docs-utils': specifier: ^1.0.16 version: 1.0.16 @@ -549,6 +546,15 @@ importers: prettier: specifier: ^3.4.2 version: 3.4.2 + react-reconciler: + specifier: ^0.32.0 + version: 0.32.0(react@19.0.0) + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 rimraf: specifier: ^5.0.10 version: 5.0.10 @@ -561,6 +567,9 @@ importers: unified: specifier: ^11.0.5 version: 11.0.5 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 webpack-bundle-analyzer: specifier: ^4.10.2 version: 4.10.2 @@ -736,7 +745,7 @@ importers: version: 0.12.5 webpack: specifier: ^5.97.1 - version: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + version: 5.97.1(webpack-cli@5.1.4) yargs: specifier: ^17.7.2 version: 17.7.2 @@ -7561,6 +7570,12 @@ packages: react-is@19.0.0: resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-reconciler@0.32.0: + resolution: {integrity: sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.1.0 + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -7733,6 +7748,9 @@ packages: remark-mdx@3.0.1: resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -7875,6 +7893,9 @@ packages: scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} @@ -10599,12 +10620,12 @@ snapshots: dependencies: '@braidai/lang': 1.1.0 - '@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)))': + '@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4))': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.1) source-map: 0.7.4 optionalDependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) transitivePeerDependencies: - acorn - supports-color @@ -10867,11 +10888,11 @@ snapshots: dependencies: glob: 10.3.10 - '@next/mdx@15.2.1(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0))': + '@next/mdx@15.2.1(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4)))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0))': dependencies: source-map: 0.7.4 optionalDependencies: - '@mdx-js/loader': 3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))) + '@mdx-js/loader': 3.1.0(acorn@8.14.1)(webpack@5.97.1(webpack-cli@5.1.4)) '@mdx-js/react': 3.1.0(@types/react@19.0.10)(react@19.0.0) '@next/swc-darwin-arm64@15.2.1': @@ -12219,19 +12240,19 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) '@xtuc/ieee754@1.2.0': {} @@ -12492,7 +12513,7 @@ snapshots: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 schema-utils: 4.3.0 - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) babel-plugin-add-import-extension@1.6.0(@babel/core@7.26.9): dependencies: @@ -12737,10 +12758,6 @@ snapshots: dependencies: chai: 4.5.0 - chai-dom@1.12.1(chai@5.2.0): - dependencies: - chai: 5.2.0 - chai@4.5.0: dependencies: assertion-error: 1.1.0 @@ -12950,7 +12967,7 @@ snapshots: dependencies: schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) compression@1.7.4: dependencies: @@ -13116,7 +13133,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) css-tree@3.1.0: dependencies: @@ -16954,7 +16971,7 @@ snapshots: postcss: 8.5.3 semver: 7.6.3 optionalDependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) transitivePeerDependencies: - typescript @@ -17173,6 +17190,11 @@ snapshots: react-is@19.0.0: {} + react-reconciler@0.32.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.26.0 + react-refresh@0.14.2: {} react-router-dom@7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): @@ -17437,6 +17459,13 @@ snapshots: transitivePeerDependencies: - supports-color + remark-mdx@3.1.0: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -17611,6 +17640,8 @@ snapshots: scheduler@0.25.0: {} + scheduler@0.26.0: {} + schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 @@ -18043,7 +18074,7 @@ snapshots: style-loader@4.0.0(webpack@5.97.1): dependencies: - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) style-to-object@0.4.4: dependencies: @@ -18180,14 +18211,14 @@ snapshots: temp-dir@1.0.0: {} - terser-webpack-plugin@5.3.12(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))): + terser-webpack-plugin@5.3.12(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) terser@5.37.0: dependencies: @@ -18745,9 +18776,9 @@ snapshots: webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.97.1) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -18756,7 +18787,7 @@ snapshots: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(webpack-cli@5.1.4) webpack-merge: 5.10.0 optionalDependencies: webpack-bundle-analyzer: 4.10.2 @@ -18769,7 +18800,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)): + webpack@5.97.1(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -18791,7 +18822,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.12(webpack@5.97.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))) + terser-webpack-plugin: 5.3.12(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: From 2f89faccd0ec70570e7380497e37884e17bfc4ce Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:37:32 +0200 Subject: [PATCH 04/40] WIP --- docs/scripts/generateLlmTxt.mjs | 2 +- docs/scripts/mdxNodeHelpers.mjs | 127 ++++++++++++++++++ docs/scripts/mdxToMarkdown.mjs | 110 +++++++-------- .../react/components/toolbar/page.mdx | 7 + pnpm-lock.yaml | 62 +++++---- 5 files changed, 225 insertions(+), 83 deletions(-) create mode 100644 docs/scripts/mdxNodeHelpers.mjs diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 3b4a821420..49eb1d8c45 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -64,7 +64,7 @@ async function generateLlmsTxt() { // Create directories for output if needed await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); - // Create markdown content with frontmatter + // Create markdown content with frontmatter and include subtitle/description both in frontmatter and as markdown const frontmatter = [ '---', `title: ${title || 'Untitled'}`, diff --git a/docs/scripts/mdxNodeHelpers.mjs b/docs/scripts/mdxNodeHelpers.mjs new file mode 100644 index 0000000000..90d0478edb --- /dev/null +++ b/docs/scripts/mdxNodeHelpers.mjs @@ -0,0 +1,127 @@ +/** + * mdxNodeHelpers.mjs - Helper functions for creating MDX AST nodes + * + * This module provides utility functions to create nodes for MDX/Markdown + * abstract syntax trees, making transformer code more readable and maintainable. + */ + +/** + * Create a text node + * @param {string} value - The text content + * @returns {Object} A text node + */ +export function text(value) { + return { + type: 'text', + value: value || '' + }; +} + +/** + * Helper to normalize children (handles string, node, or array) + * @param {string|Object|Array} children - Child content + * @returns {Array} Normalized array of nodes + */ +function normalizeChildren(children) { + // Handle empty or undefined + if (!children) { + return []; + } + + // Convert to array if not already + const childArray = Array.isArray(children) ? children : [children]; + + // Convert strings to text nodes + return childArray.map(child => + typeof child === 'string' ? text(child) : child + ); +} + +/** + * Create a paragraph node + * @param {string|Object|Array} children - Child node, string, or array of nodes/strings + * @returns {Object} A paragraph node + */ +export function paragraph(children) { + return { + type: 'paragraph', + children: normalizeChildren(children) + }; +} + +/** + * Create an emphasis (italic) node + * @param {string|Object|Array} children - Child node, string, or array of nodes/strings + * @returns {Object} An emphasis node + */ +export function emphasis(children) { + return { + type: 'emphasis', + children: normalizeChildren(children) + }; +} + +/** + * Create a strong (bold) node + * @param {string|Object|Array} children - Child node, string, or array of nodes/strings + * @returns {Object} A strong node + */ +export function strong(children) { + return { + type: 'strong', + children: normalizeChildren(children) + }; +} + +/** + * Create a heading node + * @param {number} depth - Heading level (1-6) + * @param {string|Object|Array} children - Child node, string, or array of nodes/strings + * @returns {Object} A heading node + */ +export function heading(depth, children) { + return { + type: 'heading', + depth: depth || 1, + children: normalizeChildren(children) + }; +} + +/** + * Create a code block node + * @param {string} value - Code content + * @param {string} lang - Language for syntax highlighting + * @returns {Object} A code node + */ +export function code(value, lang) { + return { + type: 'code', + lang: lang || null, + value: value || '' + }; +} + +// textParagraph has been removed as paragraph() can now handle string inputs directly + +/** + * Function to extract all text from a node and its children recursively + * @param {Object} node - AST node + * @returns {string} Extracted text content + */ +export function textContent(node) { + if (!node) return ''; + + if (typeof node === 'string') { + return node; + } + + if (node.type === 'text') { + return node.value || ''; + } + + if (node.children && Array.isArray(node.children)) { + return node.children.map(textContent).join(''); + } + + return ''; +} \ No newline at end of file diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index d265c4589f..a99208db3d 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -10,6 +10,7 @@ import remarkParse from 'remark-parse'; import remarkMdx from 'remark-mdx'; import remarkStringify from 'remark-stringify'; import { visit } from 'unist-util-visit'; +import * as mdx from './mdxNodeHelpers.mjs'; /** * Plugin to extract metadata from the MDX content @@ -20,7 +21,7 @@ function extractMetadata() { file.data.metadata = { title: '', subtitle: '', - description: '' + description: '', }; // Extract title from first h1 @@ -39,11 +40,11 @@ function extractMetadata() { } // Extract from Meta component else if (node.name === 'Meta') { - const nameAttr = node.attributes?.find(attr => - attr.name === 'name' && attr.value === 'description'); - const contentAttr = node.attributes?.find(attr => - attr.name === 'content'); - + const nameAttr = node.attributes?.find( + (attr) => attr.name === 'name' && attr.value === 'description', + ); + const contentAttr = node.attributes?.find((attr) => attr.name === 'content'); + if (nameAttr && contentAttr) { file.data.metadata.description = contentAttr.value; } @@ -55,60 +56,63 @@ function extractMetadata() { } /** - * Plugin to remove or simplify MDX-specific syntax + * Plugin to transform JSX elements to markdown or remove them from the tree */ -function simplifyMdx() { +function transformJsx() { return (tree) => { - // Handle special components by replacing them with markdown equivalents - visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { + // Handle JSX flow elements (block-level JSX) + visit(tree, ['mdxjsEsm', 'mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => { if (!parent) return; - + // Process different component types switch (node.name) { case 'Demo': - parent.children.splice(index, 1, { - type: 'paragraph', - children: [{ type: 'text', value: '--- Demo ---' }] - }); + parent.children.splice(index, 1, mdx.paragraph('--- Demo ---')); return; + case 'Reference': - parent.children.splice(index, 1, { - type: 'paragraph', - children: [{ type: 'text', value: '--- Reference ---' }] - }); + parent.children.splice(index, 1, mdx.paragraph('--- Reference ---')); return; + case 'PropsReferenceTable': - parent.children.splice(index, 1, { - type: 'paragraph', - children: [{ type: 'text', value: '--- PropsReferenceTable ---' }] - }); + parent.children.splice(index, 1, mdx.paragraph('--- PropsReferenceTable ---')); return; - case 'Subtitle': - if (node.children?.[0]?.value) { - parent.children.splice(index, 1, { - type: 'paragraph', - children: [{ - type: 'emphasis', - children: [{ type: 'text', value: node.children[0].value }] - }] - }); + + case 'Subtitle': { + // Extract text from all child nodes + const subtitleText = mdx.textContent(node); + + if (subtitleText) { + // Create a paragraph with proper emphasis node structure + parent.children.splice(index, 1, mdx.paragraph(mdx.emphasis(subtitleText))); + } else { + // Remove empty subtitle + parent.children.splice(index, 1); } return; - } - }); + } - // Remove imports and exports - visit(tree, ['mdxjsEsm', 'mdxFlowExpression'], (node, index, parent) => { - if (parent) { - parent.children.splice(index, 1); - return [visit.SKIP, index]; - } - }); + case 'Meta': { + // Check if it's a description meta tag + const nameAttr = node.attributes?.find( + (attr) => attr.name === 'name' && attr.value === 'description', + ); + const contentAttr = node.attributes?.find((attr) => attr.name === 'content'); - // Remove inline expressions - visit(tree, 'mdxTextExpression', (node, index, parent) => { - if (parent) { - parent.children.splice(index, 1); - return [visit.SKIP, index]; + if (nameAttr && contentAttr && contentAttr.value) { + // Replace with a paragraph containing the description + parent.children.splice(index, 1, mdx.paragraph(contentAttr.value)); + return; + } + + // Remove other Meta tags + parent.children.splice(index, 1); + return [visit.SKIP, index]; + } + + default: + // For other components, remove them to keep only standard markdown elements + parent.children.splice(index, 1); + return [visit.SKIP, index]; } }); @@ -128,7 +132,7 @@ export async function mdxToMarkdown(mdxContent) { .use(remarkParse) .use(remarkMdx) .use(extractMetadata) - .use(simplifyMdx) + .use(transformJsx) .use(remarkStringify, { bullet: '-', emphasis: '*', @@ -136,21 +140,21 @@ export async function mdxToMarkdown(mdxContent) { fence: '`', fences: true, listItemIndent: 'one', - rule: '-' + rule: '-', }) .process(mdxContent); // Get markdown content as string const markdown = String(file); - + // Extract metadata from the file's data const { title = '', subtitle = '', description = '' } = file.data.metadata || {}; - + return { markdown, title, subtitle, - description + description, }; } catch (error) { console.error('Error converting MDX to Markdown:', error); @@ -158,7 +162,7 @@ export async function mdxToMarkdown(mdxContent) { markdown: `Error converting MDX to Markdown: ${error.message}`, title: '', subtitle: '', - description: '' + description: '', }; } -} \ No newline at end of file +} diff --git a/docs/src/app/(public)/(content)/react/components/toolbar/page.mdx b/docs/src/app/(public)/(content)/react/components/toolbar/page.mdx index 148c7123fb..9aa6543e82 100644 --- a/docs/src/app/(public)/(content)/react/components/toolbar/page.mdx +++ b/docs/src/app/(public)/(content)/react/components/toolbar/page.mdx @@ -1,6 +1,13 @@ # Toolbar + + +ffre + A container for grouping a set of buttons and controls. + +frewf + Date: Thu, 17 Apr 2025 14:07:02 +0200 Subject: [PATCH 05/40] wip --- docs/scripts/generateLlmTxt.mjs | 3 +-- docs/scripts/mdxToMarkdown.mjs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 49eb1d8c45..627f182581 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -16,8 +16,7 @@ import { fileURLToPath } from 'url'; import glob from 'fast-glob'; import { mdxToMarkdown } from './mdxToMarkdown.mjs'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const PROJECT_ROOT = path.resolve(__dirname, '..'); +const PROJECT_ROOT = path.resolve(import.meta.dirname, '..'); const MDX_SOURCE_DIR = path.join(PROJECT_ROOT, 'src/app/(public)/(content)/react'); const OUTPUT_DIR = path.join(PROJECT_ROOT, 'llms'); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index a99208db3d..c1e0cc80fb 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -82,8 +82,8 @@ function transformJsx() { const subtitleText = mdx.textContent(node); if (subtitleText) { - // Create a paragraph with proper emphasis node structure - parent.children.splice(index, 1, mdx.paragraph(mdx.emphasis(subtitleText))); + // Create a paragraph with proper strong emphasis node structure + parent.children.splice(index, 1, mdx.paragraph(mdx.strong(subtitleText))); } else { // Remove empty subtitle parent.children.splice(index, 1); From e582ad97fd24ad3eb6d12471aef9b60d8f6aa158 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:30:05 +0200 Subject: [PATCH 06/40] wip --- docs/scripts/generateLlmTxt.mjs | 2 +- docs/scripts/mdxNodeHelpers.mjs | 20 +++++ docs/scripts/mdxToMarkdown.mjs | 155 ++++++++++++++++++++++---------- 3 files changed, 129 insertions(+), 48 deletions(-) diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 627f182581..a96b271f86 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -63,7 +63,7 @@ async function generateLlmsTxt() { // Create directories for output if needed await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); - // Create markdown content with frontmatter and include subtitle/description both in frontmatter and as markdown + // Create markdown content with frontmatter, placing subtitle/description only in frontmatter const frontmatter = [ '---', `title: ${title || 'Untitled'}`, diff --git a/docs/scripts/mdxNodeHelpers.mjs b/docs/scripts/mdxNodeHelpers.mjs index 90d0478edb..32c553bdf5 100644 --- a/docs/scripts/mdxNodeHelpers.mjs +++ b/docs/scripts/mdxNodeHelpers.mjs @@ -101,6 +101,26 @@ export function code(value, lang) { }; } +/** + * Creates a markdown table as a single string + * @param {Array} headers - Array of header strings + * @param {Array>} rows - Array of row data, each row is an array of cell content + * @returns {string} A markdown table string + */ +export function markdownTable(headers, rows) { + // Create header row + const headerRow = `| ${headers.join(' | ')} |`; + + // Create separator row + const separatorRow = `| ${headers.map(() => '-------').join(' | ')} |`; + + // Create data rows + const dataRows = rows.map(row => `| ${row.join(' | ')} |`); + + // Join all rows with newlines + return [headerRow, separatorRow, ...dataRows].join('\n'); +} + // textParagraph has been removed as paragraph() can now handle string inputs directly /** diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index c1e0cc80fb..3eb90bca54 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -61,60 +61,121 @@ function extractMetadata() { function transformJsx() { return (tree) => { // Handle JSX flow elements (block-level JSX) - visit(tree, ['mdxjsEsm', 'mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => { - if (!parent) return; - // Process different component types - switch (node.name) { - case 'Demo': - parent.children.splice(index, 1, mdx.paragraph('--- Demo ---')); - return; - - case 'Reference': - parent.children.splice(index, 1, mdx.paragraph('--- Reference ---')); - return; - - case 'PropsReferenceTable': - parent.children.splice(index, 1, mdx.paragraph('--- PropsReferenceTable ---')); - return; - - case 'Subtitle': { - // Extract text from all child nodes - const subtitleText = mdx.textContent(node); - - if (subtitleText) { - // Create a paragraph with proper strong emphasis node structure - parent.children.splice(index, 1, mdx.paragraph(mdx.strong(subtitleText))); - } else { - // Remove empty subtitle - parent.children.splice(index, 1); + visit( + tree, + [ + 'mdxJsxFlowElement', + 'mdxjsEsm', + 'mdxFlowExpression', + 'mdxTextExpression', + 'mdxJsxTextElement', + ], + (node, index, parent) => { + if (!parent) return; + // Process different component types + switch (node.name) { + case 'Demo': + parent.children.splice(index, 1, mdx.paragraph('--- Demo ---')); + return; + + case 'Reference': { + // Extract component name and parts from attributes + const componentAttr = node.attributes?.find((attr) => attr.name === 'component')?.value; + const partsAttr = node.attributes?.find((attr) => attr.name === 'parts')?.value; + + if (!componentAttr) { + parent.children.splice( + index, + 1, + mdx.paragraph('--- Reference: Missing component attribute ---'), + ); + return; + } + + // Create reference table in markdown + try { + const tables = []; + + // Add heading for API Reference + tables.push(mdx.heading(2, 'API Reference')); + + // Process each component part + const parts = partsAttr + ? partsAttr.split(/,\s*/).map((p) => p.trim()) + : [componentAttr]; + + for (const part of parts) { + // Add subheading for the part + if (parts.length > 1) { + tables.push(mdx.heading(3, part)); + } + + // Add reference table as markdown table + tables.push(mdx.paragraph(`**${part} Props**:`)); + const tableContent = mdx.markdownTable( + ['Prop', 'Type', 'Default', 'Description'], + [['(props will be generated)', 'string', '-', 'Component properties']], + ); + tables.push(mdx.paragraph(tableContent)); + + // Add separator between parts + if (parts.length > 1) { + tables.push(mdx.paragraph('')); + } + } + + // Replace the reference component with our generated tables + parent.children.splice(index, 1, ...tables); + } catch (error) { + console.error(`Error creating reference table for ${componentAttr}:`, error); + parent.children.splice( + index, + 1, + mdx.paragraph(`--- Reference Error: ${error.message} ---`), + ); + } + + return; } - return; - } - case 'Meta': { - // Check if it's a description meta tag - const nameAttr = node.attributes?.find( - (attr) => attr.name === 'name' && attr.value === 'description', - ); - const contentAttr = node.attributes?.find((attr) => attr.name === 'content'); + case 'PropsReferenceTable': + parent.children.splice(index, 1, mdx.paragraph('--- PropsReferenceTable ---')); + return; + + case 'Subtitle': { + // Extract text from all child nodes + const subtitleText = mdx.textContent(node); - if (nameAttr && contentAttr && contentAttr.value) { - // Replace with a paragraph containing the description - parent.children.splice(index, 1, mdx.paragraph(contentAttr.value)); + // Subtitle is now in frontmatter, so remove from the content + parent.children.splice(index, 1); return; } - // Remove other Meta tags - parent.children.splice(index, 1); - return [visit.SKIP, index]; - } + case 'Meta': { + // Check if it's a description meta tag + const nameAttr = node.attributes?.find( + (attr) => attr.name === 'name' && attr.value === 'description', + ); + const contentAttr = node.attributes?.find((attr) => attr.name === 'content'); - default: - // For other components, remove them to keep only standard markdown elements - parent.children.splice(index, 1); - return [visit.SKIP, index]; - } - }); + if (nameAttr && contentAttr && contentAttr.value) { + // Replace with a paragraph containing the description + parent.children.splice(index, 1, mdx.paragraph(contentAttr.value)); + return; + } + + // Remove other Meta tags + parent.children.splice(index, 1); + return [visit.SKIP, index]; + } + + default: + // For other components, remove them to keep only standard markdown elements + parent.children.splice(index, 1); + return [visit.SKIP, index]; + } + }, + ); return tree; }; From 944ae034e3543bd696f935a5c8c2f67e28d16afb Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:09:15 +0200 Subject: [PATCH 07/40] WIP --- docs/scripts/mdxToMarkdown.mjs | 124 +++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 12 deletions(-) diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 3eb90bca54..eb1587443c 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -11,6 +11,9 @@ import remarkMdx from 'remark-mdx'; import remarkStringify from 'remark-stringify'; import { visit } from 'unist-util-visit'; import * as mdx from './mdxNodeHelpers.mjs'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; /** * Plugin to extract metadata from the MDX content @@ -103,26 +106,123 @@ function transformJsx() { const parts = partsAttr ? partsAttr.split(/,\s*/).map((p) => p.trim()) : [componentAttr]; - + + // Load component definitions from JSON files + const componentDefs = []; + const kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + const projectRoot = path.resolve(import.meta.dirname, '..'); + for (const part of parts) { + try { + // Construct file path for this component part + let filename = `${kebabCase(componentAttr)}-${kebabCase(part)}.json`; + let filepath = path.join(projectRoot, 'reference/generated', filename); + + // If file doesn't exist, try with just the part name + if (!fs.existsSync(filepath)) { + filename = `${kebabCase(part)}.json`; + filepath = path.join(projectRoot, 'reference/generated', filename); + } + + // Read and parse JSON file + if (fs.existsSync(filepath)) { + const jsonContent = fs.readFileSync(filepath, 'utf-8'); + const componentDef = JSON.parse(jsonContent); + componentDefs.push(componentDef); + } else { + console.warn(`Reference file not found for ${part}`); + componentDefs.push({ + name: part, + description: '', + props: {}, + dataAttributes: {}, + cssVariables: {} + }); + } + } catch (err) { + console.error(`Error loading reference file for ${part}:`, err); + componentDefs.push({ + name: part, + description: '', + props: {}, + dataAttributes: {}, + cssVariables: {} + }); + } + } + + // Generate markdown tables for each component + componentDefs.forEach((def, idx) => { + const part = parts[idx]; + // Add subheading for the part if (parts.length > 1) { tables.push(mdx.heading(3, part)); } - - // Add reference table as markdown table - tables.push(mdx.paragraph(`**${part} Props**:`)); - const tableContent = mdx.markdownTable( - ['Prop', 'Type', 'Default', 'Description'], - [['(props will be generated)', 'string', '-', 'Component properties']], - ); - tables.push(mdx.paragraph(tableContent)); - + + // Add description if available + if (def.description) { + tables.push(mdx.paragraph(def.description)); + } + + // Props table + if (Object.keys(def.props || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} Props**:`)); + + const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ + propName, + propDef.type || '-', + propDef.default || '-', + propDef.description || '-' + ]); + + const tableContent = mdx.markdownTable( + ['Prop', 'Type', 'Default', 'Description'], + propsRows + ); + tables.push(mdx.paragraph(tableContent)); + } + + // Data attributes table + if (Object.keys(def.dataAttributes || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} Data Attributes**:`)); + + const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ + attrName, + attrDef.type || '-', + attrDef.description || '-' + ]); + + const tableContent = mdx.markdownTable( + ['Attribute', 'Type', 'Description'], + attrRows + ); + tables.push(mdx.paragraph(tableContent)); + } + + // CSS variables table + if (Object.keys(def.cssVariables || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} CSS Variables**:`)); + + const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ + varName, + varDef.type || '-', + varDef.default || '-', + varDef.description || '-' + ]); + + const tableContent = mdx.markdownTable( + ['Variable', 'Type', 'Default', 'Description'], + cssRows + ); + tables.push(mdx.paragraph(tableContent)); + } + // Add separator between parts - if (parts.length > 1) { + if (parts.length > 1 && idx < parts.length - 1) { tables.push(mdx.paragraph('')); } - } + }); // Replace the reference component with our generated tables parent.children.splice(index, 1, ...tables); From 8557ab1f1b562b7a85b35613d96a7fe6c87adbf4 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:18:17 +0200 Subject: [PATCH 08/40] wip --- docs/scripts/mdxNodeHelpers.mjs | 17 +++- docs/scripts/mdxToMarkdown.mjs | 148 +-------------------------- docs/scripts/referenceProcessor.mjs | 151 ++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 docs/scripts/referenceProcessor.mjs diff --git a/docs/scripts/mdxNodeHelpers.mjs b/docs/scripts/mdxNodeHelpers.mjs index 32c553bdf5..cbc878bbdd 100644 --- a/docs/scripts/mdxNodeHelpers.mjs +++ b/docs/scripts/mdxNodeHelpers.mjs @@ -105,14 +105,25 @@ export function code(value, lang) { * Creates a markdown table as a single string * @param {Array} headers - Array of header strings * @param {Array>} rows - Array of row data, each row is an array of cell content + * @param {Array} [alignment] - Optional array of alignments ('left', 'center', 'right') for each column * @returns {string} A markdown table string */ -export function markdownTable(headers, rows) { +export function markdownTable(headers, rows, alignment = null) { // Create header row const headerRow = `| ${headers.join(' | ')} |`; - // Create separator row - const separatorRow = `| ${headers.map(() => '-------').join(' | ')} |`; + // Create separator row with alignment + const separators = headers.map((_, index) => { + if (!alignment || !alignment[index]) return '-------'; + + switch(alignment[index]) { + case 'center': return ':-----:'; + case 'right': return '------:'; + default: return ':------'; // left alignment is default + } + }); + + const separatorRow = `| ${separators.join(' | ')} |`; // Create data rows const dataRows = rows.map(row => `| ${row.join(' | ')} |`); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index eb1587443c..bec513f1fb 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -14,6 +14,7 @@ import * as mdx from './mdxNodeHelpers.mjs'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { processReference } from './referenceProcessor.mjs'; /** * Plugin to extract metadata from the MDX content @@ -82,159 +83,20 @@ function transformJsx() { return; case 'Reference': { - // Extract component name and parts from attributes - const componentAttr = node.attributes?.find((attr) => attr.name === 'component')?.value; - const partsAttr = node.attributes?.find((attr) => attr.name === 'parts')?.value; - - if (!componentAttr) { - parent.children.splice( - index, - 1, - mdx.paragraph('--- Reference: Missing component attribute ---'), - ); - return; - } - - // Create reference table in markdown try { - const tables = []; - - // Add heading for API Reference - tables.push(mdx.heading(2, 'API Reference')); + // Process the reference component using our dedicated processor + const tables = processReference(node, parent, index); - // Process each component part - const parts = partsAttr - ? partsAttr.split(/,\s*/).map((p) => p.trim()) - : [componentAttr]; - - // Load component definitions from JSON files - const componentDefs = []; - const kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - const projectRoot = path.resolve(import.meta.dirname, '..'); - - for (const part of parts) { - try { - // Construct file path for this component part - let filename = `${kebabCase(componentAttr)}-${kebabCase(part)}.json`; - let filepath = path.join(projectRoot, 'reference/generated', filename); - - // If file doesn't exist, try with just the part name - if (!fs.existsSync(filepath)) { - filename = `${kebabCase(part)}.json`; - filepath = path.join(projectRoot, 'reference/generated', filename); - } - - // Read and parse JSON file - if (fs.existsSync(filepath)) { - const jsonContent = fs.readFileSync(filepath, 'utf-8'); - const componentDef = JSON.parse(jsonContent); - componentDefs.push(componentDef); - } else { - console.warn(`Reference file not found for ${part}`); - componentDefs.push({ - name: part, - description: '', - props: {}, - dataAttributes: {}, - cssVariables: {} - }); - } - } catch (err) { - console.error(`Error loading reference file for ${part}:`, err); - componentDefs.push({ - name: part, - description: '', - props: {}, - dataAttributes: {}, - cssVariables: {} - }); - } - } - - // Generate markdown tables for each component - componentDefs.forEach((def, idx) => { - const part = parts[idx]; - - // Add subheading for the part - if (parts.length > 1) { - tables.push(mdx.heading(3, part)); - } - - // Add description if available - if (def.description) { - tables.push(mdx.paragraph(def.description)); - } - - // Props table - if (Object.keys(def.props || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} Props**:`)); - - const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ - propName, - propDef.type || '-', - propDef.default || '-', - propDef.description || '-' - ]); - - const tableContent = mdx.markdownTable( - ['Prop', 'Type', 'Default', 'Description'], - propsRows - ); - tables.push(mdx.paragraph(tableContent)); - } - - // Data attributes table - if (Object.keys(def.dataAttributes || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} Data Attributes**:`)); - - const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ - attrName, - attrDef.type || '-', - attrDef.description || '-' - ]); - - const tableContent = mdx.markdownTable( - ['Attribute', 'Type', 'Description'], - attrRows - ); - tables.push(mdx.paragraph(tableContent)); - } - - // CSS variables table - if (Object.keys(def.cssVariables || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} CSS Variables**:`)); - - const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ - varName, - varDef.type || '-', - varDef.default || '-', - varDef.description || '-' - ]); - - const tableContent = mdx.markdownTable( - ['Variable', 'Type', 'Default', 'Description'], - cssRows - ); - tables.push(mdx.paragraph(tableContent)); - } - - // Add separator between parts - if (parts.length > 1 && idx < parts.length - 1) { - tables.push(mdx.paragraph('')); - } - }); - - // Replace the reference component with our generated tables + // Replace the reference component with the generated tables parent.children.splice(index, 1, ...tables); } catch (error) { - console.error(`Error creating reference table for ${componentAttr}:`, error); + console.error(`Error processing Reference component:`, error); parent.children.splice( index, 1, mdx.paragraph(`--- Reference Error: ${error.message} ---`), ); } - return; } diff --git a/docs/scripts/referenceProcessor.mjs b/docs/scripts/referenceProcessor.mjs new file mode 100644 index 0000000000..01f1bc8354 --- /dev/null +++ b/docs/scripts/referenceProcessor.mjs @@ -0,0 +1,151 @@ +/** + * referenceProcessor.mjs - Process component reference definitions + * + * This module handles loading and converting component reference data + * from JSON files into markdown tables for documentation. + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import * as mdx from './mdxNodeHelpers.mjs'; + +/** + * Transforms a Reference component into markdown tables + * @param {Object} node - The Reference JSX node from MDX + * @param {Array} ancestors - The ancestry chain of the node + * @returns {Array} Array of markdown nodes to replace the Reference component + */ +export function processReference(node, parent, index) { + // Extract component name and parts from attributes + const componentAttr = node.attributes?.find((attr) => attr.name === 'component')?.value; + const partsAttr = node.attributes?.find((attr) => attr.name === 'parts')?.value; + + if (!componentAttr) { + throw new Error('Missing "component" prop on the "" component.'); + } + + const tables = []; + + // Add heading for API Reference + tables.push(mdx.heading(2, 'API Reference')); + + // Process each component part + const parts = partsAttr + ? partsAttr.split(/,\s*/).map((p) => p.trim()) + : [componentAttr]; + + // Load component definitions from JSON files + const componentDefs = []; + const kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + const projectRoot = path.resolve(import.meta.dirname, '..'); + + for (const part of parts) { + // Construct file path for this component part + let filename = `${kebabCase(componentAttr)}-${kebabCase(part)}.json`; + let filepath = path.join(projectRoot, 'reference/generated', filename); + + // If file doesn't exist, try with just the part name + if (!fs.existsSync(filepath)) { + filename = `${kebabCase(part)}.json`; + filepath = path.join(projectRoot, 'reference/generated', filename); + } + + // Read and parse JSON file + if (!fs.existsSync(filepath)) { + throw new Error(`Reference file not found for component ${componentAttr}, part ${part}`); + } + + const jsonContent = fs.readFileSync(filepath, 'utf-8'); + const componentDef = JSON.parse(jsonContent); + componentDefs.push(componentDef); + } + + // Generate markdown tables for each component + componentDefs.forEach((def, idx) => { + const part = parts[idx]; + + // Add subheading for the part + if (parts.length > 1) { + tables.push(mdx.heading(3, part)); + } + + // Add description if available + if (def.description) { + tables.push(mdx.paragraph(def.description)); + } + + // Props table + if (Object.keys(def.props || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} Props**:`)); + + const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ + propName, + propDef.type || '-', + propDef.default || '-', + propDef.description || '-' + ]); + + // Define column alignments: prop name left-aligned, others left-aligned + const alignments = ['left', 'left', 'left', 'left']; + + const tableContent = mdx.markdownTable( + ['Prop', 'Type', 'Default', 'Description'], + propsRows, + alignments + ); + tables.push(mdx.paragraph(tableContent)); + } + + // Data attributes table + if (Object.keys(def.dataAttributes || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} Data Attributes**:`)); + + const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ + attrName, + attrDef.type || '-', + attrDef.description || '-' + ]); + + // Define column alignments + const alignments = ['left', 'left', 'left']; + + const tableContent = mdx.markdownTable( + ['Attribute', 'Type', 'Description'], + attrRows, + alignments + ); + tables.push(mdx.paragraph(tableContent)); + } + + // CSS variables table + if (Object.keys(def.cssVariables || {}).length > 0) { + tables.push(mdx.paragraph(`**${part} CSS Variables**:`)); + + const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ + varName, + varDef.type || '-', + varDef.default || '-', + varDef.description || '-' + ]); + + // Define column alignments + const alignments = ['left', 'left', 'left', 'left']; + + const tableContent = mdx.markdownTable( + ['Variable', 'Type', 'Default', 'Description'], + cssRows, + alignments + ); + tables.push(mdx.paragraph(tableContent)); + } + + // Add separator between parts + if (parts.length > 1 && idx < parts.length - 1) { + tables.push(mdx.paragraph('')); + } + }); + + return tables; +} \ No newline at end of file From 28291ed24a91b379d1603cc129cbb44e5c746051 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:42:20 +0200 Subject: [PATCH 09/40] wip --- docs/scripts/mdxNodeHelpers.mjs | 151 ++++++++++++++++++++-------- docs/scripts/mdxToMarkdown.mjs | 5 + docs/scripts/referenceProcessor.mjs | 76 +++++++++++--- 3 files changed, 177 insertions(+), 55 deletions(-) diff --git a/docs/scripts/mdxNodeHelpers.mjs b/docs/scripts/mdxNodeHelpers.mjs index cbc878bbdd..ad8da199fe 100644 --- a/docs/scripts/mdxNodeHelpers.mjs +++ b/docs/scripts/mdxNodeHelpers.mjs @@ -1,6 +1,6 @@ /** * mdxNodeHelpers.mjs - Helper functions for creating MDX AST nodes - * + * * This module provides utility functions to create nodes for MDX/Markdown * abstract syntax trees, making transformer code more readable and maintainable. */ @@ -13,7 +13,7 @@ export function text(value) { return { type: 'text', - value: value || '' + value: value || '', }; } @@ -27,14 +27,12 @@ function normalizeChildren(children) { if (!children) { return []; } - + // Convert to array if not already const childArray = Array.isArray(children) ? children : [children]; - + // Convert strings to text nodes - return childArray.map(child => - typeof child === 'string' ? text(child) : child - ); + return childArray.map((child) => (typeof child === 'string' ? text(child) : child)); } /** @@ -45,7 +43,7 @@ function normalizeChildren(children) { export function paragraph(children) { return { type: 'paragraph', - children: normalizeChildren(children) + children: normalizeChildren(children), }; } @@ -57,7 +55,7 @@ export function paragraph(children) { export function emphasis(children) { return { type: 'emphasis', - children: normalizeChildren(children) + children: normalizeChildren(children), }; } @@ -69,7 +67,7 @@ export function emphasis(children) { export function strong(children) { return { type: 'strong', - children: normalizeChildren(children) + children: normalizeChildren(children), }; } @@ -83,7 +81,7 @@ export function heading(depth, children) { return { type: 'heading', depth: depth || 1, - children: normalizeChildren(children) + children: normalizeChildren(children), }; } @@ -97,39 +95,112 @@ export function code(value, lang) { return { type: 'code', lang: lang || null, - value: value || '' + value: value || '', + }; +} + +/** + * Create an inline code node + * @param {string} value - Code content + * @returns {Object} An inline code node + */ +export function inlineCode(value) { + return { + type: 'inlineCode', + value: value || '', + }; +} + +/** + * Creates a table cell node + * @param {string|Object} content - Cell content + * @returns {Object} Table cell node + */ +function tableCell(content) { + let children; + + // Handle different content types + if (typeof content === 'string') { + // Convert string to text node + children = [text(content)]; + } else if (content && content.type) { + // Use node directly + children = [content]; + } else if (Array.isArray(content)) { + // Process array of nodes + children = content.map((c) => (typeof c === 'string' ? text(c) : c)); + } else if (content === null || content === undefined) { + // Handle null/undefined + children = [text('-')]; + } else { + // Unexpected content type + throw new Error(`Unexpected content type in table cell: ${typeof content}`); + } + + return { + type: 'tableCell', + children, + }; +} + +/** + * Creates a table row node + * @param {Array} cells - Array of cell contents + * @returns {Object} Table row node + */ +function tableRow(cells) { + return { + type: 'tableRow', + children: cells.map((cell) => tableCell(cell)), }; } /** - * Creates a markdown table as a single string - * @param {Array} headers - Array of header strings - * @param {Array>} rows - Array of row data, each row is an array of cell content + * Creates a markdown table node (GFM) + * @param {Array} headers - Array of header strings or nodes + * @param {Array>} rows - Array of row data, each row is an array of cell content * @param {Array} [alignment] - Optional array of alignments ('left', 'center', 'right') for each column - * @returns {string} A markdown table string + * @returns {Object} A table node */ -export function markdownTable(headers, rows, alignment = null) { - // Create header row - const headerRow = `| ${headers.join(' | ')} |`; - - // Create separator row with alignment - const separators = headers.map((_, index) => { - if (!alignment || !alignment[index]) return '-------'; - - switch(alignment[index]) { - case 'center': return ':-----:'; - case 'right': return '------:'; - default: return ':------'; // left alignment is default +export function table(headers, rows, alignment = null) { + // Convert alignment strings to AST format + const align = headers.map((_, index) => { + if (!alignment || !alignment[index]) return null; + + switch (alignment[index]) { + case 'center': + return 'center'; + case 'right': + return 'right'; + default: + return 'left'; } }); - - const separatorRow = `| ${separators.join(' | ')} |`; - + + // Create header row + const headerRow = tableRow(headers); + // Create data rows - const dataRows = rows.map(row => `| ${row.join(' | ')} |`); - - // Join all rows with newlines - return [headerRow, separatorRow, ...dataRows].join('\n'); + const dataRows = rows.map((row) => tableRow(row)); + + // Return table node + return { + type: 'table', + align, + children: [headerRow, ...dataRows], + }; +} + +/** + * Backwards compatibility function for existing code + * Creates a markdown table node + * @param {Array} headers - Array of header strings or nodes + * @param {Array>} rows - Array of row data, each row is an array of cell content + * @param {Array} [alignment] - Optional array of alignments ('left', 'center', 'right') for each column + * @returns {Object} A table node + */ +export function markdownTable(headers, rows, alignment = null) { + return table(headers, rows, alignment); } // textParagraph has been removed as paragraph() can now handle string inputs directly @@ -141,18 +212,18 @@ export function markdownTable(headers, rows, alignment = null) { */ export function textContent(node) { if (!node) return ''; - + if (typeof node === 'string') { return node; } - + if (node.type === 'text') { return node.value || ''; } - + if (node.children && Array.isArray(node.children)) { return node.children.map(textContent).join(''); } - + return ''; -} \ No newline at end of file +} diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index bec513f1fb..1f7d4ced59 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -8,6 +8,7 @@ import { unified } from 'unified'; import remarkParse from 'remark-parse'; import remarkMdx from 'remark-mdx'; +import remarkGfm from 'remark-gfm'; import remarkStringify from 'remark-stringify'; import { visit } from 'unist-util-visit'; import * as mdx from './mdxNodeHelpers.mjs'; @@ -154,6 +155,7 @@ export async function mdxToMarkdown(mdxContent) { const file = await unified() .use(remarkParse) .use(remarkMdx) + .use(remarkGfm) // Add GitHub Flavored Markdown support .use(extractMetadata) .use(transformJsx) .use(remarkStringify, { @@ -164,6 +166,9 @@ export async function mdxToMarkdown(mdxContent) { fences: true, listItemIndent: 'one', rule: '-', + // Enable GitHub Flavored Markdown features + commonmark: true, + gfm: true }) .process(mdxContent); diff --git a/docs/scripts/referenceProcessor.mjs b/docs/scripts/referenceProcessor.mjs index 01f1bc8354..d595e9e74b 100644 --- a/docs/scripts/referenceProcessor.mjs +++ b/docs/scripts/referenceProcessor.mjs @@ -8,9 +8,53 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkGfm from 'remark-gfm'; import * as mdx from './mdxNodeHelpers.mjs'; +/** + * Parse a markdown string into an AST + * @param {string} markdown - Markdown string to parse + * @returns {Object} The root content node of the parsed AST + */ +function parseMarkdown(markdown) { + if (!markdown || markdown === '-') { + return mdx.text('-'); + } + + try { + // Parse markdown into an AST + const processor = unified() + .use(remarkParse) + .use(remarkGfm); + + const result = processor.parse(markdown); + + // If there's only one paragraph with one child, just return that child + if (result.children.length === 1 && + result.children[0].type === 'paragraph' && + result.children[0].children.length === 1) { + return result.children[0].children[0]; + } + + // Otherwise return all children wrapped in a paragraph + if (result.children.length === 1 && result.children[0].type === 'paragraph') { + return result.children[0]; + } + + // Return the whole tree + return { + type: 'paragraph', + children: result.children + }; + } catch (error) { + console.error('Error parsing markdown:', error); + return mdx.text(markdown); // Fallback to plain text + } +} + /** * Transforms a Reference component into markdown tables * @param {Object} node - The Reference JSX node from MDX @@ -73,7 +117,9 @@ export function processReference(node, parent, index) { // Add description if available if (def.description) { - tables.push(mdx.paragraph(def.description)); + // Parse the description as markdown + const descriptionNode = parseMarkdown(def.description); + tables.push(descriptionNode); } // Props table @@ -82,20 +128,20 @@ export function processReference(node, parent, index) { const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ propName, - propDef.type || '-', - propDef.default || '-', - propDef.description || '-' + propDef.type ? mdx.inlineCode(propDef.type) : '-', + propDef.default ? mdx.inlineCode(propDef.default) : '-', + parseMarkdown(propDef.description || '-') ]); // Define column alignments: prop name left-aligned, others left-aligned const alignments = ['left', 'left', 'left', 'left']; - const tableContent = mdx.markdownTable( + const tableNode = mdx.table( ['Prop', 'Type', 'Default', 'Description'], propsRows, alignments ); - tables.push(mdx.paragraph(tableContent)); + tables.push(tableNode); } // Data attributes table @@ -104,19 +150,19 @@ export function processReference(node, parent, index) { const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ attrName, - attrDef.type || '-', - attrDef.description || '-' + attrDef.type ? mdx.inlineCode(attrDef.type) : '-', + parseMarkdown(attrDef.description || '-') ]); // Define column alignments const alignments = ['left', 'left', 'left']; - const tableContent = mdx.markdownTable( + const tableNode = mdx.table( ['Attribute', 'Type', 'Description'], attrRows, alignments ); - tables.push(mdx.paragraph(tableContent)); + tables.push(tableNode); } // CSS variables table @@ -125,20 +171,20 @@ export function processReference(node, parent, index) { const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ varName, - varDef.type || '-', - varDef.default || '-', - varDef.description || '-' + varDef.type ? mdx.inlineCode(varDef.type) : '-', + varDef.default ? mdx.inlineCode(varDef.default) : '-', + parseMarkdown(varDef.description || '-') ]); // Define column alignments const alignments = ['left', 'left', 'left', 'left']; - const tableContent = mdx.markdownTable( + const tableNode = mdx.table( ['Variable', 'Type', 'Default', 'Description'], cssRows, alignments ); - tables.push(mdx.paragraph(tableContent)); + tables.push(tableNode); } // Add separator between parts From 937184267cd0e96359b701d9b49878b8dde02086 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:44:46 +0200 Subject: [PATCH 10/40] subtitles --- docs/scripts/referenceProcessor.mjs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/scripts/referenceProcessor.mjs b/docs/scripts/referenceProcessor.mjs index d595e9e74b..473bf52d12 100644 --- a/docs/scripts/referenceProcessor.mjs +++ b/docs/scripts/referenceProcessor.mjs @@ -124,7 +124,11 @@ export function processReference(node, parent, index) { // Props table if (Object.keys(def.props || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} Props**:`)); + // Create a proper heading with strong node + tables.push(mdx.paragraph([ + mdx.strong(`${part} Props`), + mdx.text(':') + ])); const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ propName, @@ -146,7 +150,10 @@ export function processReference(node, parent, index) { // Data attributes table if (Object.keys(def.dataAttributes || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} Data Attributes**:`)); + tables.push(mdx.paragraph([ + mdx.strong(`${part} Data Attributes`), + mdx.text(':') + ])); const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ attrName, @@ -167,7 +174,10 @@ export function processReference(node, parent, index) { // CSS variables table if (Object.keys(def.cssVariables || {}).length > 0) { - tables.push(mdx.paragraph(`**${part} CSS Variables**:`)); + tables.push(mdx.paragraph([ + mdx.strong(`${part} CSS Variables`), + mdx.text(':') + ])); const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ varName, From 18cf4472c71d77736101799afc8395baa61f03db Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:46:09 +0200 Subject: [PATCH 11/40] update --- docs/scripts/mdxToMarkdown.mjs | 6 +- docs/scripts/referenceProcessor.mjs | 114 ++++++++++++---------------- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 1f7d4ced59..0b8e240ef6 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -155,7 +155,7 @@ export async function mdxToMarkdown(mdxContent) { const file = await unified() .use(remarkParse) .use(remarkMdx) - .use(remarkGfm) // Add GitHub Flavored Markdown support + .use(remarkGfm) // Add GitHub Flavored Markdown support .use(extractMetadata) .use(transformJsx) .use(remarkStringify, { @@ -167,8 +167,8 @@ export async function mdxToMarkdown(mdxContent) { listItemIndent: 'one', rule: '-', // Enable GitHub Flavored Markdown features - commonmark: true, - gfm: true + commonmark: true, + gfm: true, }) .process(mdxContent); diff --git a/docs/scripts/referenceProcessor.mjs b/docs/scripts/referenceProcessor.mjs index 473bf52d12..a21423d915 100644 --- a/docs/scripts/referenceProcessor.mjs +++ b/docs/scripts/referenceProcessor.mjs @@ -1,6 +1,6 @@ /** * referenceProcessor.mjs - Process component reference definitions - * + * * This module handles loading and converting component reference data * from JSON files into markdown tables for documentation. */ @@ -26,28 +26,28 @@ function parseMarkdown(markdown) { try { // Parse markdown into an AST - const processor = unified() - .use(remarkParse) - .use(remarkGfm); - + const processor = unified().use(remarkParse).use(remarkGfm); + const result = processor.parse(markdown); - + // If there's only one paragraph with one child, just return that child - if (result.children.length === 1 && - result.children[0].type === 'paragraph' && - result.children[0].children.length === 1) { + if ( + result.children.length === 1 && + result.children[0].type === 'paragraph' && + result.children[0].children.length === 1 + ) { return result.children[0].children[0]; } - + // Otherwise return all children wrapped in a paragraph if (result.children.length === 1 && result.children[0].type === 'paragraph') { return result.children[0]; } - + // Return the whole tree return { type: 'paragraph', - children: result.children + children: result.children, }; } catch (error) { console.error('Error parsing markdown:', error); @@ -65,143 +65,125 @@ export function processReference(node, parent, index) { // Extract component name and parts from attributes const componentAttr = node.attributes?.find((attr) => attr.name === 'component')?.value; const partsAttr = node.attributes?.find((attr) => attr.name === 'parts')?.value; - + if (!componentAttr) { throw new Error('Missing "component" prop on the "" component.'); } - + const tables = []; - - // Add heading for API Reference - tables.push(mdx.heading(2, 'API Reference')); - + // Process each component part - const parts = partsAttr - ? partsAttr.split(/,\s*/).map((p) => p.trim()) - : [componentAttr]; - + const parts = partsAttr ? partsAttr.split(/,\s*/).map((p) => p.trim()) : [componentAttr]; + // Load component definitions from JSON files const componentDefs = []; const kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); const projectRoot = path.resolve(import.meta.dirname, '..'); - + for (const part of parts) { // Construct file path for this component part let filename = `${kebabCase(componentAttr)}-${kebabCase(part)}.json`; let filepath = path.join(projectRoot, 'reference/generated', filename); - + // If file doesn't exist, try with just the part name if (!fs.existsSync(filepath)) { filename = `${kebabCase(part)}.json`; filepath = path.join(projectRoot, 'reference/generated', filename); } - + // Read and parse JSON file if (!fs.existsSync(filepath)) { throw new Error(`Reference file not found for component ${componentAttr}, part ${part}`); } - + const jsonContent = fs.readFileSync(filepath, 'utf-8'); const componentDef = JSON.parse(jsonContent); componentDefs.push(componentDef); } - + // Generate markdown tables for each component componentDefs.forEach((def, idx) => { const part = parts[idx]; - + // Add subheading for the part if (parts.length > 1) { tables.push(mdx.heading(3, part)); } - + // Add description if available if (def.description) { // Parse the description as markdown const descriptionNode = parseMarkdown(def.description); tables.push(descriptionNode); } - + // Props table if (Object.keys(def.props || {}).length > 0) { // Create a proper heading with strong node - tables.push(mdx.paragraph([ - mdx.strong(`${part} Props`), - mdx.text(':') - ])); - + tables.push(mdx.paragraph([mdx.strong(`${part} Props`), mdx.text(':')])); + const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ propName, propDef.type ? mdx.inlineCode(propDef.type) : '-', propDef.default ? mdx.inlineCode(propDef.default) : '-', - parseMarkdown(propDef.description || '-') + parseMarkdown(propDef.description || '-'), ]); - + // Define column alignments: prop name left-aligned, others left-aligned const alignments = ['left', 'left', 'left', 'left']; - + const tableNode = mdx.table( ['Prop', 'Type', 'Default', 'Description'], propsRows, - alignments + alignments, ); tables.push(tableNode); } - + // Data attributes table if (Object.keys(def.dataAttributes || {}).length > 0) { - tables.push(mdx.paragraph([ - mdx.strong(`${part} Data Attributes`), - mdx.text(':') - ])); - + tables.push(mdx.paragraph([mdx.strong(`${part} Data Attributes`), mdx.text(':')])); + const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ attrName, attrDef.type ? mdx.inlineCode(attrDef.type) : '-', - parseMarkdown(attrDef.description || '-') + parseMarkdown(attrDef.description || '-'), ]); - + // Define column alignments const alignments = ['left', 'left', 'left']; - - const tableNode = mdx.table( - ['Attribute', 'Type', 'Description'], - attrRows, - alignments - ); + + const tableNode = mdx.table(['Attribute', 'Type', 'Description'], attrRows, alignments); tables.push(tableNode); } - + // CSS variables table if (Object.keys(def.cssVariables || {}).length > 0) { - tables.push(mdx.paragraph([ - mdx.strong(`${part} CSS Variables`), - mdx.text(':') - ])); - + tables.push(mdx.paragraph([mdx.strong(`${part} CSS Variables`), mdx.text(':')])); + const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ varName, varDef.type ? mdx.inlineCode(varDef.type) : '-', varDef.default ? mdx.inlineCode(varDef.default) : '-', - parseMarkdown(varDef.description || '-') + parseMarkdown(varDef.description || '-'), ]); - + // Define column alignments const alignments = ['left', 'left', 'left', 'left']; - + const tableNode = mdx.table( ['Variable', 'Type', 'Default', 'Description'], cssRows, - alignments + alignments, ); tables.push(tableNode); } - + // Add separator between parts if (parts.length > 1 && idx < parts.length - 1) { tables.push(mdx.paragraph('')); } }); - + return tables; -} \ No newline at end of file +} From 8c8fe49e1c5fa2eadd283d6c6260d64a6dfc8f22 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:02:53 +0200 Subject: [PATCH 12/40] demos --- docs/scripts/demoProcessor.mjs | 126 ++++++++++++++++++++++++++++++++ docs/scripts/generateLlmTxt.mjs | 2 +- docs/scripts/mdxToMarkdown.mjs | 27 +++++-- 3 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 docs/scripts/demoProcessor.mjs diff --git a/docs/scripts/demoProcessor.mjs b/docs/scripts/demoProcessor.mjs new file mode 100644 index 0000000000..2387e890d1 --- /dev/null +++ b/docs/scripts/demoProcessor.mjs @@ -0,0 +1,126 @@ +/** + * demoProcessor.mjs - Process demo component directories + * + * This module handles loading and converting demo code examples + * into markdown code blocks for documentation. + */ + +import fs from 'fs'; +import path from 'path'; +import * as mdx from './mdxNodeHelpers.mjs'; + +/** + * Read all files from a directory + * @param {string} directory - The directory to read + * @returns {Array} Array of file paths + */ +function readDirFiles(directory) { + try { + return fs + .readdirSync(directory) + .filter((file) => !fs.statSync(path.join(directory, file)).isDirectory()) + .map((file) => path.join(directory, file)); + } catch (error) { + console.error(`Error reading directory ${directory}:`, error); + return []; + } +} + +/** + * Create a code block for a file with a comment header + * @param {string} filePath - Path to the file + * @param {string} relativePath - Relative path to show in the comment + * @returns {Object} Code node with the file content + */ +function createFileCodeBlock(filePath, relativePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const extension = path.extname(filePath).slice(1); + + // Add comment header with filename + const commentedContent = `/* ${relativePath} */\n${content}`; + + // Create code block with appropriate language + return mdx.code(commentedContent, extension); +} + +/** + * Transforms a Demo component into markdown code blocks + * @param {Object} node - The Demo JSX node from MDX + * @param {string} mdxFilePath - Path to the MDX file containing the Demo component + * @returns {Array} Array of markdown nodes to replace the Demo component + */ +export function processDemo(node, mdxFilePath) { + // Extract path attribute + const pathAttr = node.attributes?.find((attr) => attr.name === 'path')?.value; + + if (!pathAttr) { + throw new Error('Missing "path" prop on the "" component.'); + } + + // Resolve demo path relative to the MDX file + const mdxDir = path.dirname(mdxFilePath); + const demoPath = path.resolve(mdxDir, pathAttr); + + // Check if the demo folder exists + if (!fs.existsSync(demoPath)) { + throw new Error(`Demo folder not found at "${demoPath}"`); + } + + // Define paths for CSS Modules and Tailwind folders + const cssModulesPath = path.join(demoPath, 'css-modules'); + const tailwindPath = path.join(demoPath, 'tailwind'); + + // Check if at least one of the folders exists + const hasCssModules = fs.existsSync(cssModulesPath); + const hasTailwind = fs.existsSync(tailwindPath); + + // Throw error if neither folder exists + if (!hasCssModules && !hasTailwind) { + throw new Error(`Neither CSS Modules nor Tailwind folders found at "${demoPath}"`); + } + + const result = []; + + // Add main Demo heading + result.push(mdx.heading(2, 'Demo')); + + // Process CSS Modules section if it exists + if (hasCssModules) { + result.push(mdx.heading(3, 'CSS Modules')); + + // Add brief explanation paragraph + result.push(mdx.paragraph( + 'This example shows how to implement the component using CSS Modules.' + )); + + // Get all files in the CSS Modules folder + const cssModulesFiles = readDirFiles(cssModulesPath); + + // Add code blocks for each file + cssModulesFiles.forEach((file) => { + const relativePath = path.relative(cssModulesPath, file); + result.push(createFileCodeBlock(file, relativePath)); + }); + } + + // Process Tailwind section if it exists + if (hasTailwind) { + result.push(mdx.heading(3, 'Tailwind')); + + // Add brief explanation paragraph + result.push(mdx.paragraph( + 'This example shows how to implement the component using Tailwind CSS.' + )); + + // Get all files in the Tailwind folder + const tailwindFiles = readDirFiles(tailwindPath); + + // Add code blocks for each file + tailwindFiles.forEach((file) => { + const relativePath = path.relative(tailwindPath, file); + result.push(createFileCodeBlock(file, relativePath)); + }); + } + + return result; +} diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index a96b271f86..a5f9b4a2b3 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -55,7 +55,7 @@ async function generateLlmsTxt() { const mdxContent = await fs.readFile(mdxFile, 'utf-8'); // Convert to markdown and extract metadata - const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent); + const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent, mdxFile); // Get output file path const outputFilePath = path.join(OUTPUT_DIR, `${dirPath.replace(/\\/g, '-')}.md`); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 0b8e240ef6..e8325f190c 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -16,6 +16,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { processReference } from './referenceProcessor.mjs'; +import { processDemo } from './demoProcessor.mjs'; /** * Plugin to extract metadata from the MDX content @@ -64,7 +65,7 @@ function extractMetadata() { * Plugin to transform JSX elements to markdown or remove them from the tree */ function transformJsx() { - return (tree) => { + return (tree, file) => { // Handle JSX flow elements (block-level JSX) visit( tree, @@ -79,9 +80,17 @@ function transformJsx() { if (!parent) return; // Process different component types switch (node.name) { - case 'Demo': - parent.children.splice(index, 1, mdx.paragraph('--- Demo ---')); + case 'Demo': { + // Get the file path for context + const filePath = file.path || ''; + + // Process the demo component using our dedicated processor + const demoContent = processDemo(node, filePath); + + // Replace the demo component with the generated content + parent.children.splice(index, 1, ...demoContent); return; + } case 'Reference': { try { @@ -147,11 +156,17 @@ function transformJsx() { /** * Converts MDX content to markdown and extracts metadata * @param {string} mdxContent - The MDX content to convert + * @param {string} filePath - Optional path to the MDX file for context * @returns {Promise} An object containing the markdown and metadata */ -export async function mdxToMarkdown(mdxContent) { +export async function mdxToMarkdown(mdxContent, filePath) { try { - // Process the MDX content + // Process the MDX content and include file path for context + const vfile = { + path: filePath, + value: mdxContent, + }; + const file = await unified() .use(remarkParse) .use(remarkMdx) @@ -170,7 +185,7 @@ export async function mdxToMarkdown(mdxContent) { commonmark: true, gfm: true, }) - .process(mdxContent); + .process(vfile); // Get markdown content as string const markdown = String(file); From f0d51916c345da4d63e25251346c5e073c576bda Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:08:56 +0200 Subject: [PATCH 13/40] prettier --- docs/scripts/mdxToMarkdown.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index e8325f190c..5063a0ed4f 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -17,6 +17,7 @@ import path from 'path'; import { fileURLToPath } from 'url'; import { processReference } from './referenceProcessor.mjs'; import { processDemo } from './demoProcessor.mjs'; +import * as prettier from 'prettier'; /** * Plugin to extract metadata from the MDX content @@ -188,7 +189,14 @@ export async function mdxToMarkdown(mdxContent, filePath) { .process(vfile); // Get markdown content as string - const markdown = String(file); + let markdown = String(file); + + // Format markdown with prettier + const prettierConfig = await prettier.resolveConfig(process.cwd()); + markdown = await prettier.format(markdown, { + ...prettierConfig, + parser: 'markdown' + }); // Extract metadata from the file's data const { title = '', subtitle = '', description = '' } = file.data.metadata || {}; From a319cc52915996d2245a556cda2be697414a18d8 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:31:12 +0200 Subject: [PATCH 14/40] generate it --- docs/scripts/generateLlmTxt.mjs | 180 ++++++++++++++++++++++---------- docs/scripts/mdxToMarkdown.mjs | 12 +-- 2 files changed, 129 insertions(+), 63 deletions(-) diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index a5f9b4a2b3..7bdbe64dc4 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -15,10 +15,12 @@ import path from 'path'; import { fileURLToPath } from 'url'; import glob from 'fast-glob'; import { mdxToMarkdown } from './mdxToMarkdown.mjs'; +import * as prettier from 'prettier'; const PROJECT_ROOT = path.resolve(import.meta.dirname, '..'); const MDX_SOURCE_DIR = path.join(PROJECT_ROOT, 'src/app/(public)/(content)/react'); -const OUTPUT_DIR = path.join(PROJECT_ROOT, 'llms'); +const OUTPUT_BASE_DIR = path.join(PROJECT_ROOT, 'llms'); +const OUTPUT_REACT_DIR = path.join(OUTPUT_BASE_DIR, 'react'); /** * Generate llms.txt and markdown files from MDX content @@ -27,69 +29,135 @@ async function generateLlmsTxt() { console.log('Generating llms.txt and markdown files...'); try { - // Create output directory if it doesn't exist - await fs.mkdir(OUTPUT_DIR, { recursive: true }); - - // Find all MDX files - const mdxFiles = await glob('**/*/page.mdx', { - cwd: MDX_SOURCE_DIR, - absolute: true, - }); - - console.log(`Found ${mdxFiles.length} MDX files`); + // Create output directories if they don't exist + await fs.mkdir(OUTPUT_BASE_DIR, { recursive: true }); + await fs.mkdir(OUTPUT_REACT_DIR, { recursive: true }); // Generate llms.txt entries const llmsEntries = []; - // Process each MDX file - for (const mdxFile of mdxFiles) { - // Get relative path for URL generation - const relativePath = path.relative(MDX_SOURCE_DIR, mdxFile); - const dirPath = path.dirname(relativePath); - - // Create URL for llms.txt (without /page.mdx) - const urlPath = dirPath.replace(/\\/g, '/'); - const url = `https://base-ui.org/react/${urlPath}`; - - // Read MDX content - const mdxContent = await fs.readFile(mdxFile, 'utf-8'); - - // Convert to markdown and extract metadata - const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent, mdxFile); - - // Get output file path - const outputFilePath = path.join(OUTPUT_DIR, `${dirPath.replace(/\\/g, '-')}.md`); - - // Create directories for output if needed - await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); - - // Create markdown content with frontmatter, placing subtitle/description only in frontmatter - const frontmatter = [ - '---', - `title: ${title || 'Untitled'}`, - subtitle ? `subtitle: ${subtitle}` : '', - description ? `description: ${description}` : '', - '---', - '', - markdown, - ] - .filter(Boolean) - .join('\n'); - - // Write markdown file - await fs.writeFile(outputFilePath, frontmatter, 'utf-8'); + // Store metadata for each section + const metadataBySection = { + overview: [], + handbook: [], + components: [], + utils: [], + }; + + // Process files from a specific section + async function processSection(sectionName) { + console.log(`Processing ${sectionName} section...`); + + // Find all MDX files in this section + const sectionPath = path.join(MDX_SOURCE_DIR, sectionName); + const mdxFiles = await glob('**/*/page.mdx', { + cwd: sectionPath, + absolute: true, + }); + + console.log(`Found ${mdxFiles.length} files in ${sectionName}`); + + // Process each MDX file in this section + for (const mdxFile of mdxFiles) { + // Get relative path for URL generation + const relativePath = path.relative(MDX_SOURCE_DIR, mdxFile); + const dirPath = path.dirname(relativePath); + + // Create URL for llms.txt (without /page.mdx) + const urlPath = dirPath.replace(/\\/g, '/'); + const url = `https://base-ui.org/react/${urlPath}`; + + // Read MDX content + const mdxContent = await fs.readFile(mdxFile, 'utf-8'); + + // Convert to markdown and extract metadata + const { markdown, title, subtitle, description } = await mdxToMarkdown(mdxContent, mdxFile); + + // Get output file path - maintain the original directory structure under react + const outputFilePath = path.join(OUTPUT_REACT_DIR, `${dirPath}.md`); + + // Create directories for output if needed + await fs.mkdir(path.dirname(outputFilePath), { recursive: true }); + + // Create markdown content with frontmatter + const frontmatter = [ + '---', + `title: ${title || 'Untitled'}`, + subtitle ? `subtitle: ${subtitle}` : '', + description ? `description: ${description}` : '', + '---', + '', + markdown, + ] + .filter(Boolean) + .join('\n'); + + // Write markdown file + await fs.writeFile(outputFilePath, frontmatter, 'utf-8'); + + // Add entry to llms.txt + llmsEntries.push(`${url} ${outputFilePath}`); + + // Store metadata for this file in the appropriate section + metadataBySection[sectionName].push({ + title: title || 'Untitled', + subtitle: subtitle || '', + urlPath: `./react/${urlPath}`, + }); + + console.log(`Processed: ${relativePath}`); + } + } - // Add entry to llms.txt - llmsEntries.push(`${url} ${outputFilePath}`); + // Process each section + await processSection('overview'); + await processSection('handbook'); + await processSection('components'); + await processSection('utils'); + + // Build structured content for llms.txt + const sections = ['# Base UI', '']; + + // Create formatted sections in specific order + const formatSection = (items, title) => { + if (items.length > 0) { + sections.push(`## ${title}`, ''); + + // Add each item as a link with subtitle, starting with a bullet (-) + items.forEach((item) => { + sections.push(`- [${item.title}](${item.urlPath}): ${item.subtitle}`); + }); + + sections.push(''); // Add empty line after section + } + }; + + // Add sections in the required order + formatSection(metadataBySection.overview, 'Overview'); + formatSection(metadataBySection.handbook, 'Handbook'); + formatSection(metadataBySection.components, 'Components'); + formatSection(metadataBySection.utils, 'Utilities'); + + // Create llms.txt content and format with prettier + let llmsTxtContent = sections.join('\n'); + + // Apply prettier formatting + const prettierConfig = await prettier.resolveConfig(process.cwd()); + llmsTxtContent = await prettier.format(llmsTxtContent, { + ...prettierConfig, + parser: 'markdown', + }); - console.log(`Processed: ${relativePath}`); - } + await fs.writeFile(path.join(OUTPUT_BASE_DIR, 'llms.txt'), llmsTxtContent, 'utf-8'); - // Create llms.txt - const llmsTxtContent = llmsEntries.join('\n'); - await fs.writeFile(path.join(OUTPUT_DIR, 'llms.txt'), llmsTxtContent, 'utf-8'); + // Calculate the total number of files processed + const totalFiles = + metadataBySection.overview.length + + metadataBySection.handbook.length + + metadataBySection.components.length + + metadataBySection.utils.length; - console.log(`Successfully generated ${mdxFiles.length} markdown files and llms.txt`); + console.log(`Successfully generated ${totalFiles} markdown files and llms.txt`); } catch (error) { console.error('Error generating llms.txt:', error); process.exit(1); diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index 5063a0ed4f..c8f5dc8861 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -40,10 +40,11 @@ function extractMetadata() { }); // Extract from MDX components - visit(tree, 'mdxJsxFlowElement', (node) => { + visit(tree, ['mdxJsxFlowElement', 'mdxFlowExpression', 'mdxJsxTextElement'], (node) => { // Extract from Subtitle component - if (node.name === 'Subtitle' && node.children?.[0]?.value) { - file.data.metadata.subtitle = node.children[0].value; + if (node.name === 'Subtitle') { + const subtitleText = mdx.textContent(node); + file.data.metadata.subtitle = subtitleText; } // Extract from Meta component else if (node.name === 'Meta') { @@ -116,9 +117,6 @@ function transformJsx() { return; case 'Subtitle': { - // Extract text from all child nodes - const subtitleText = mdx.textContent(node); - // Subtitle is now in frontmatter, so remove from the content parent.children.splice(index, 1); return; @@ -195,7 +193,7 @@ export async function mdxToMarkdown(mdxContent, filePath) { const prettierConfig = await prettier.resolveConfig(process.cwd()); markdown = await prettier.format(markdown, { ...prettierConfig, - parser: 'markdown' + parser: 'markdown', }); // Extract metadata from the file's data From 0214cf4aa5fe43ad7847cd6d47716fd1b5079b3d Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:55:38 +0200 Subject: [PATCH 15/40] frwfwgr --- docs/scripts/generateLlmTxt.mjs | 10 ------ docs/scripts/mdxToMarkdown.mjs | 23 ++++---------- docs/scripts/referenceProcessor.mjs | 47 ++++++----------------------- 3 files changed, 15 insertions(+), 65 deletions(-) diff --git a/docs/scripts/generateLlmTxt.mjs b/docs/scripts/generateLlmTxt.mjs index 7bdbe64dc4..fd836decf2 100755 --- a/docs/scripts/generateLlmTxt.mjs +++ b/docs/scripts/generateLlmTxt.mjs @@ -1,15 +1,5 @@ #!/usr/bin/env node -/** - * generateLlmTxt.mjs - Generates llms.txt and markdown files from MDX content - * - * This script performs the following: - * 1. Scans all MDX files in the docs/src/app/(public)/(content)/react folder - * 2. Converts each MDX file to markdown using a custom React reconciler - * 3. Outputs the files to docs/llms directory - * 4. Creates llms.txt according to https://llmstxt.org/ format - */ - import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; diff --git a/docs/scripts/mdxToMarkdown.mjs b/docs/scripts/mdxToMarkdown.mjs index c8f5dc8861..9a0e07b33e 100644 --- a/docs/scripts/mdxToMarkdown.mjs +++ b/docs/scripts/mdxToMarkdown.mjs @@ -79,7 +79,6 @@ function transformJsx() { 'mdxJsxTextElement', ], (node, index, parent) => { - if (!parent) return; // Process different component types switch (node.name) { case 'Demo': { @@ -95,20 +94,12 @@ function transformJsx() { } case 'Reference': { - try { - // Process the reference component using our dedicated processor - const tables = processReference(node, parent, index); - - // Replace the reference component with the generated tables - parent.children.splice(index, 1, ...tables); - } catch (error) { - console.error(`Error processing Reference component:`, error); - parent.children.splice( - index, - 1, - mdx.paragraph(`--- Reference Error: ${error.message} ---`), - ); - } + // Process the reference component using our dedicated processor + const tables = processReference(node, parent, index); + + // Replace the reference component with the generated tables + parent.children.splice(index, 1, ...tables); + return; } @@ -117,7 +108,6 @@ function transformJsx() { return; case 'Subtitle': { - // Subtitle is now in frontmatter, so remove from the content parent.children.splice(index, 1); return; } @@ -180,7 +170,6 @@ export async function mdxToMarkdown(mdxContent, filePath) { fences: true, listItemIndent: 'one', rule: '-', - // Enable GitHub Flavored Markdown features commonmark: true, gfm: true, }) diff --git a/docs/scripts/referenceProcessor.mjs b/docs/scripts/referenceProcessor.mjs index a21423d915..8759a86212 100644 --- a/docs/scripts/referenceProcessor.mjs +++ b/docs/scripts/referenceProcessor.mjs @@ -20,39 +20,10 @@ import * as mdx from './mdxNodeHelpers.mjs'; * @returns {Object} The root content node of the parsed AST */ function parseMarkdown(markdown) { - if (!markdown || markdown === '-') { - return mdx.text('-'); - } - - try { - // Parse markdown into an AST - const processor = unified().use(remarkParse).use(remarkGfm); - - const result = processor.parse(markdown); - - // If there's only one paragraph with one child, just return that child - if ( - result.children.length === 1 && - result.children[0].type === 'paragraph' && - result.children[0].children.length === 1 - ) { - return result.children[0].children[0]; - } - - // Otherwise return all children wrapped in a paragraph - if (result.children.length === 1 && result.children[0].type === 'paragraph') { - return result.children[0]; - } - - // Return the whole tree - return { - type: 'paragraph', - children: result.children, - }; - } catch (error) { - console.error('Error parsing markdown:', error); - return mdx.text(markdown); // Fallback to plain text - } + // Parse markdown into an AST + const processor = unified().use(remarkParse); + const result = processor.parse(markdown); + return result.children; } /** @@ -61,7 +32,7 @@ function parseMarkdown(markdown) { * @param {Array} ancestors - The ancestry chain of the node * @returns {Array} Array of markdown nodes to replace the Reference component */ -export function processReference(node, parent, index) { +export function processReference(node) { // Extract component name and parts from attributes const componentAttr = node.attributes?.find((attr) => attr.name === 'component')?.value; const partsAttr = node.attributes?.find((attr) => attr.name === 'parts')?.value; @@ -114,13 +85,13 @@ export function processReference(node, parent, index) { if (def.description) { // Parse the description as markdown const descriptionNode = parseMarkdown(def.description); - tables.push(descriptionNode); + tables.push(mdx.paragraph(descriptionNode)); } // Props table if (Object.keys(def.props || {}).length > 0) { // Create a proper heading with strong node - tables.push(mdx.paragraph([mdx.strong(`${part} Props`), mdx.text(':')])); + tables.push(mdx.paragraph([mdx.strong(`${part} Props:`)])); const propsRows = Object.entries(def.props).map(([propName, propDef]) => [ propName, @@ -142,7 +113,7 @@ export function processReference(node, parent, index) { // Data attributes table if (Object.keys(def.dataAttributes || {}).length > 0) { - tables.push(mdx.paragraph([mdx.strong(`${part} Data Attributes`), mdx.text(':')])); + tables.push(mdx.paragraph([mdx.strong(`${part} Data Attributes:`)])); const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [ attrName, @@ -159,7 +130,7 @@ export function processReference(node, parent, index) { // CSS variables table if (Object.keys(def.cssVariables || {}).length > 0) { - tables.push(mdx.paragraph([mdx.strong(`${part} CSS Variables`), mdx.text(':')])); + tables.push(mdx.paragraph([mdx.strong(`${part} CSS Variables:`)])); const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [ varName, From b0427f8f0c3cd0692c62931c93a3f96b9d266e46 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:55:49 +0200 Subject: [PATCH 16/40] llms --- docs/llms/llms.txt | 49 + docs/llms/react/components/accordion.md | 360 +++ docs/llms/react/components/alert-dialog.md | 804 ++++++ docs/llms/react/components/avatar.md | 155 + docs/llms/react/components/checkbox-group.md | 248 ++ docs/llms/react/components/checkbox.md | 224 ++ docs/llms/react/components/collapsible.md | 239 ++ docs/llms/react/components/dialog.md | 1145 ++++++++ docs/llms/react/components/field.md | 290 ++ docs/llms/react/components/fieldset.md | 195 ++ docs/llms/react/components/form.md | 524 ++++ docs/llms/react/components/input.md | 100 + docs/llms/react/components/menu.md | 2716 ++++++++++++++++++ docs/llms/react/components/number-field.md | 517 ++++ docs/llms/react/components/popover.md | 507 ++++ docs/llms/react/components/preview-card.md | 457 +++ docs/llms/react/components/progress.md | 190 ++ docs/llms/react/components/radio.md | 278 ++ docs/llms/react/components/scroll-area.md | 282 ++ docs/llms/react/components/select.md | 824 ++++++ docs/llms/react/components/separator.md | 162 ++ docs/llms/react/components/slider.md | 299 ++ docs/llms/react/components/switch.md | 223 ++ docs/llms/react/components/tabs.md | 424 +++ docs/llms/react/components/toggle-group.md | 280 ++ docs/llms/react/components/toggle.md | 234 ++ docs/llms/react/components/toolbar.md | 787 +++++ docs/llms/react/components/tooltip.md | 581 ++++ docs/llms/react/handbook/animation.md | 205 ++ docs/llms/react/handbook/composition.md | 97 + docs/llms/react/handbook/styling.md | 135 + docs/llms/react/overview/about.md | 58 + docs/llms/react/overview/accessibility.md | 45 + docs/llms/react/overview/quick-start.md | 324 +++ docs/llms/react/overview/releases.md | 219 ++ docs/llms/react/utils/direction-provider.md | 125 + docs/llms/react/utils/use-render.md | 328 +++ 37 files changed, 14630 insertions(+) create mode 100644 docs/llms/llms.txt create mode 100644 docs/llms/react/components/accordion.md create mode 100644 docs/llms/react/components/alert-dialog.md create mode 100644 docs/llms/react/components/avatar.md create mode 100644 docs/llms/react/components/checkbox-group.md create mode 100644 docs/llms/react/components/checkbox.md create mode 100644 docs/llms/react/components/collapsible.md create mode 100644 docs/llms/react/components/dialog.md create mode 100644 docs/llms/react/components/field.md create mode 100644 docs/llms/react/components/fieldset.md create mode 100644 docs/llms/react/components/form.md create mode 100644 docs/llms/react/components/input.md create mode 100644 docs/llms/react/components/menu.md create mode 100644 docs/llms/react/components/number-field.md create mode 100644 docs/llms/react/components/popover.md create mode 100644 docs/llms/react/components/preview-card.md create mode 100644 docs/llms/react/components/progress.md create mode 100644 docs/llms/react/components/radio.md create mode 100644 docs/llms/react/components/scroll-area.md create mode 100644 docs/llms/react/components/select.md create mode 100644 docs/llms/react/components/separator.md create mode 100644 docs/llms/react/components/slider.md create mode 100644 docs/llms/react/components/switch.md create mode 100644 docs/llms/react/components/tabs.md create mode 100644 docs/llms/react/components/toggle-group.md create mode 100644 docs/llms/react/components/toggle.md create mode 100644 docs/llms/react/components/toolbar.md create mode 100644 docs/llms/react/components/tooltip.md create mode 100644 docs/llms/react/handbook/animation.md create mode 100644 docs/llms/react/handbook/composition.md create mode 100644 docs/llms/react/handbook/styling.md create mode 100644 docs/llms/react/overview/about.md create mode 100644 docs/llms/react/overview/accessibility.md create mode 100644 docs/llms/react/overview/quick-start.md create mode 100644 docs/llms/react/overview/releases.md create mode 100644 docs/llms/react/utils/direction-provider.md create mode 100644 docs/llms/react/utils/use-render.md diff --git a/docs/llms/llms.txt b/docs/llms/llms.txt new file mode 100644 index 0000000000..b4d1633c63 --- /dev/null +++ b/docs/llms/llms.txt @@ -0,0 +1,49 @@ +# Base UI + +## Overview + +- [About Base UI](./react/overview/about): An open-source React component library for building accessible user interfaces. +- [Accessibility](./react/overview/accessibility): Learn how to make the most of Base UI's accessibility features and guidelines. +- [Quick start](./react/overview/quick-start): A quick guide to getting started with Base UI. +- [Releases](./react/overview/releases): Changelogs for each Base UI release. + +## Handbook + +- [Animation](./react/handbook/animation): A guide to animating Base UI components. +- [Composition](./react/handbook/composition): A guide to composing Base UI components with your own React components. +- [Styling](./react/handbook/styling): A guide to styling Base UI components with your preferred styling engine. + +## Components + +- [Accordion](./react/components/accordion): A set of collapsible panels with headings. +- [Alert Dialog](./react/components/alert-dialog): A dialog that requires user response to proceed. +- [Checkbox](./react/components/checkbox): An easily stylable checkbox component. +- [Avatar](./react/components/avatar): An easily stylable avatar component. +- [Checkbox Group](./react/components/checkbox-group): Provides shared state to a series of checkboxes. +- [Collapsible](./react/components/collapsible): A collapsible panel controlled by a button. +- [Dialog](./react/components/dialog): A popup that opens on top of the entire page. +- [Field](./react/components/field): A component that provides labelling and validation for form controls. +- [Fieldset](./react/components/fieldset): A native fieldset element with an easily stylable legend. +- [Form](./react/components/form): A native form element with consolidated error handling. +- [Input](./react/components/input): A native input element that automatically works with Field. +- [Menu](./react/components/menu): A list of actions in a dropdown, enhanced with keyboard navigation. +- [Number Field](./react/components/number-field): A numeric input element with increment and decrement buttons, and a scrub area. +- [Popover](./react/components/popover): An accessible popup anchored to a button. +- [Preview Card](./react/components/preview-card): A popup that appears when a link is hovered, showing a preview for sighted users. +- [Progress](./react/components/progress): Displays the status of a task that takes a long time. +- [Radio](./react/components/radio): An easily stylable radio button component. +- [Scroll Area](./react/components/scroll-area): A native scroll container with custom scrollbars. +- [Select](./react/components/select): A common form component for choosing a predefined value in a dropdown menu. +- [Separator](./react/components/separator): A separator element accessible to screen readers. +- [Slider](./react/components/slider): An easily stylable range input. +- [Switch](./react/components/switch): A control that indicates whether a setting is on or off. +- [Tabs](./react/components/tabs): A component for toggling between related panels on the same page. +- [Toggle](./react/components/toggle): A two-state button that can be on or off. +- [Toggle Group](./react/components/toggle-group): Provides a shared state to a series of toggle buttons. +- [Tooltip](./react/components/tooltip): A popup that appears when an element is hovered or focused, showing a hint for sighted users. +- [Toolbar](./react/components/toolbar): A container for grouping a set of buttons and controls. + +## Utilities + +- [Direction Provider](./react/utils/direction-provider): Enables RTL behavior for Base UI components. +- [useRender](./react/utils/use-render): Hook for enabling a render prop in custom components. diff --git a/docs/llms/react/components/accordion.md b/docs/llms/react/components/accordion.md new file mode 100644 index 0000000000..579b271b27 --- /dev/null +++ b/docs/llms/react/components/accordion.md @@ -0,0 +1,360 @@ +--- +title: Accordion +subtitle: A set of collapsible panels with headings. +description: A high-quality, unstyled React accordion component that displays a set of collapsible panels with headings. +--- +# Accordion + +A high-quality, unstyled React accordion component that displays a set of collapsible panels with headings. + +## Demo + +### CSS Modules + +This example shows how to implement the component using CSS Modules. + +```css +/* index.module.css */ +.Accordion { + box-sizing: border-box; + display: flex; + width: 24rem; + max-width: calc(100vw - 8rem); + flex-direction: column; + justify-content: center; + color: var(--color-gray-900); +} + +.Item { + border-bottom: 1px solid var(--color-gray-200); +} + +.Header { + margin: 0; +} + +.Trigger { + box-sizing: border-box; + display: flex; + width: 100%; + gap: 1rem; + align-items: baseline; + justify-content: space-between; + padding: 0.5rem 0; + color: var(--color-gray-900); + font-family: inherit; + font-weight: 500; + font-size: 1rem; + line-height: 1.5rem; + background: none; + border: none; + outline: none; + cursor: pointer; + text-align: left; + + &:focus-visible { + outline: 2px solid var(--color-blue); + } +} + +.TriggerIcon { + box-sizing: border-box; + flex-shrink: 0; + width: 0.75rem; + height: 0.75rem; + margin-right: 0.5rem; + transition: transform 150ms ease-out; + + [data-panel-open] > & { + transform: rotate(45deg) scale(1.1); + } +} + +.Panel { + box-sizing: border-box; + height: var(--accordion-panel-height); + overflow: hidden; + color: var(--color-gray-600); + font-size: 1rem; + line-height: 1.5rem; + transition: height 150ms ease-out; + + &[data-starting-style], + &[data-ending-style] { + height: 0; + } +} + +.Content { + padding-bottom: 0.75rem; +} +``` + +```tsx +/* index.tsx */ +import * as React from "react"; +import { Accordion } from "@base-ui-components/react/accordion"; +import styles from "./index.module.css"; + +export default function ExampleAccordion() { + return ( + + + + + What is Base UI? + + + + +
+ Base UI is a library of high-quality unstyled React components for + design systems and web apps. +
+
+
+ + + + + How do I get started? + + + + +
+ Head to the “Quick start” guide in the docs. If you’ve used unstyled + libraries before, you’ll feel at home. +
+
+
+ + + + + Can I use it for my project? + + + + +
+ Of course! Base UI is free and open source. +
+
+
+
+ ); +} + +function PlusIcon(props: React.ComponentProps<"svg">) { + return ( + + + + ); +} +``` + +### Tailwind + +This example shows how to implement the component using Tailwind CSS. + +```tsx +/* index.tsx */ +import * as React from "react"; +import { Accordion } from "@base-ui-components/react/accordion"; + +export default function ExampleAccordion() { + return ( + + + + + What is Base UI? + + + + +
+ Base UI is a library of high-quality unstyled React components for + design systems and web apps. +
+
+
+ + + + + How do I get started? + + + + +
+ Head to the “Quick start” guide in the docs. If you’ve used unstyled + libraries before, you’ll feel at home. +
+
+
+ + + + + Can I use it for my project? + + + + +
+ Of course! Base UI is free and open source. +
+
+
+
+ ); +} + +function PlusIcon(props: React.ComponentProps<"svg">) { + return ( + + + + ); +} +``` + +## API reference + +Import the component and assemble its parts: + +```jsx title="Anatomy" +import { Accordion } from "@base-ui-components/react/accordion"; + + + + + + + + +; +``` + +### Root + +Groups all parts of the accordion. +Renders a `
` element. + +**Root Props:** + +| Prop | Type | Default | Description | +| :--------------- | :----------------------------------------------------------- | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| defaultValue | `array` | - | The uncontrolled value of the item(s) that should be initially expanded.To render a controlled accordion, use the `value` prop instead. | +| value | `array` | - | The controlled value of the item(s) that should be expanded.To render an uncontrolled accordion, use the `defaultValue` prop instead. | +| onValueChange | `(value) => void` | - | Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument. | +| hiddenUntilFound | `boolean` | `false` | Allows the browser’s built-in page search to find and expand the panel contents.Overrides the `keepMounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. | +| openMultiple | `boolean` | `true` | Whether multiple items can be open at the same time. | +| disabled | `boolean` | `false` | Whether the component should ignore user interaction. | +| loop | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. | +| orientation | `'horizontal' \| 'vertical'` | `'vertical'` | The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. | +| className | `string \| (state) => string` | - | CSS class applied to the element, or a function that returns a class based on the component’s state. | +| keepMounted | `boolean` | `false` | Whether to keep the element in the DOM while the panel is closed. This prop is ignored when `hiddenUntilFound` is used. | +| render | `React.ReactElement \| (props, state) => React.ReactElement` | - | Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. | + +**Root Data Attributes:** + +| Attribute | Type | Description | +| :------------ | :--- | :-------------------------------------- | +| data-disabled | - | Present when the accordion is disabled. | + +### Item + +Groups an accordion header with the corresponding panel. +Renders a `
` element. + +**Item Props:** + +| Prop | Type | Default | Description | +| :----------- | :----------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| onOpenChange | `(open) => void` | - | Event handler called when the panel is opened or closed. | +| disabled | `boolean` | `false` | Whether the component should ignore user interaction. | +| className | `string \| (state) => string` | - | CSS class applied to the element, or a function that returns a class based on the component’s state. | +| render | `React.ReactElement \| (props, state) => React.ReactElement` | - | Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. | + +**Item Data Attributes:** + +| Attribute | Type | Description | +| :------------ | :------- | :------------------------------------------- | +| data-open | - | Present when the accordion item is open. | +| data-disabled | - | Present when the accordion item is disabled. | +| data-index | `number` | Indicates the index of the accordion item. | + +### Header + +A heading that labels the corresponding panel. +Renders an `

` element. + +**Header Props:** + +| Prop | Type | Default | Description | +| :-------- | :----------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| className | `string \| (state) => string` | - | CSS class applied to the element, or a function that returns a class based on the component’s state. | +| render | `React.ReactElement \| (props, state) => React.ReactElement` | - | Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. | + +**Header Data Attributes:** + +| Attribute | Type | Description | +| :------------ | :------- | :------------------------------------------- | +| data-open | - | Present when the accordion item is open. | +| data-disabled | - | Present when the accordion item is disabled. | +| data-index | `number` | Indicates the index of the accordion item. | + +### Trigger + +A button that opens and closes the corresponding panel. +Renders a `