From 394e18fe38398ced0e8ba416393dba5a76569fe2 Mon Sep 17 00:00:00 2001 From: dav-is Date: Mon, 30 Jun 2025 01:00:16 -0400 Subject: [PATCH 001/210] Add CodeHighlighter component for loading demos and codeblocks --- packages/docs-infra/package.json | 1 + .../src/CodeHighlighter/CodeHighlighter.tsx | 566 ++++++++++++++++++ .../CodeHighlighter/CodeHighlighterClient.tsx | 11 + .../src/CodeHighlighter/demos/code/Code.tsx | 18 + .../CodeHighlighter/demos/demo/createDemo.tsx | 72 +++ .../src/CodeHighlighter/index.test.tsx | 70 +++ .../docs-infra/src/CodeHighlighter/index.ts | 3 + .../HighlightProvider/HighlightContext.tsx | 16 + .../HighlightProvider/HighlightProvider.tsx | 22 + .../docs-infra/src/HighlightProvider/index.ts | 3 + packages/docs-infra/tsconfig.json | 1 + pnpm-lock.yaml | 28 + 12 files changed, 811 insertions(+) create mode 100644 packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/CodeHighlighterClient.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/index.test.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/index.ts create mode 100644 packages/docs-infra/src/HighlightProvider/HighlightContext.tsx create mode 100644 packages/docs-infra/src/HighlightProvider/HighlightProvider.tsx create mode 100644 packages/docs-infra/src/HighlightProvider/index.ts diff --git a/packages/docs-infra/package.json b/packages/docs-infra/package.json index a42b73703..7277d61ef 100644 --- a/packages/docs-infra/package.json +++ b/packages/docs-infra/package.json @@ -39,6 +39,7 @@ "dependencies": { "@babel/runtime": "^7.27.6", "@types/hast": "^3.0.4", + "@wooorm/starry-night": "^3.8.0", "clipboard-copy": "^4.0.1", "hast": "^1.0.0", "hast-util-to-jsx-runtime": "^2.3.6", diff --git a/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx b/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx new file mode 100644 index 000000000..2a08944a5 --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx @@ -0,0 +1,566 @@ +import * as React from 'react'; +import { Fragment, jsx, jsxs } from 'react/jsx-runtime'; +import { toText } from 'hast-util-to-text'; +import { toJsxRuntime } from 'hast-util-to-jsx-runtime'; + +import type { Nodes as HastNodes } from 'hast'; + +export type Components = { [key: string]: React.ReactNode }; + +type CodeMeta = { + fileName: string; +}; +type VariantExtraFiles = { + [fileName: string]: null | (CodeMeta & { source?: string | HastNodes | { hastJson: string } }); +}; +type VariantCode = CodeMeta & { + source?: string | HastNodes | { hastJson: string }; + extraFiles?: VariantExtraFiles; +}; +type Code = { [key: string]: VariantCode }; +type ParsedVariantCode = CodeMeta & { + source: HastNodes | { hastJson: string }; + extraFiles: { [fileName: string]: CodeMeta & { source: HastNodes | { hastJson: string } } }; +}; +type ParsedCode = { [key: string]: ParsedVariantCode }; + +type Options = { name?: string; slug?: string; description?: string }; +export type ContentProps = { code: ParsedCode; components?: Components } & Options; +export type ContentLoadingProps = { fileNames: string[]; source: React.ReactNode }; + +type ErrorHandler = React.ComponentType<{ error: Error }>; + +export type CodeHighlighterClientProps = Options & { + Content: React.ComponentType; + code?: Code; + components?: Components; + variant?: string; + defaultVariant?: string; + ErrorHandler?: ErrorHandler; + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + url?: string; + fallback?: React.ReactNode; +}; + +export type CodeHighlighterInnerProps = Options & { + Content: React.ComponentType; + code?: Code; + components?: Components; + variant?: string; + defaultVariant?: string; + ErrorHandler?: ErrorHandler; + clientOnly?: boolean; + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + url?: string; + fallback?: React.ReactNode; + loadVariantCode?: (variantName: string, url?: string) => Promise; + loadSource?: (variantName: string, fileName: string, url?: string) => Promise; + parseSource?: (source: string) => Promise; +}; + +type CodeHighlighterWithInitialSourceProps = Options & { + Content: React.ComponentType; + code: Code; // e + initialVariant: string; // e + initialFilename: string; // e + initialSource: string | HastNodes | { hastJson: string }; // e + initialExtraFiles?: VariantExtraFiles; // e + components?: Components; + variant?: string; + defaultVariant?: string; + ContentLoading: React.ComponentType; // e + ErrorHandler?: ErrorHandler; + clientOnly?: boolean; + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + fallbackUsesExtraFiles?: boolean; + fallbackUsesAllVariants?: boolean; + url?: string; + loadVariantCode?: (variantName: string, url?: string) => Promise; + loadSource?: (variantName: string, fileName: string, url?: string) => Promise; + parseSource?: (source: string) => Promise; +}; + +type CodeInitialSourceLoaderProps = Options & { + Content: React.ComponentType; + code?: Code; + components?: Components; + variant?: string; + initialVariant: string; // e + initial?: VariantCode; // e + defaultVariant?: string; + precompute?: boolean | Code; + ContentLoading: React.ComponentType; + ErrorHandler?: ErrorHandler; + clientOnly?: boolean; + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + fallbackUsesExtraFiles?: boolean; + fallbackUsesAllVariants?: boolean; + url?: string; + loadVariantCode?: (variantName: string, url?: string) => Promise; + loadSource?: (variantName: string, fileName: string, url?: string) => Promise; + parseSource?: (source: string) => Promise; +}; + +export type CodeHighlighterProps = Options & { + Content: React.ComponentType; + code?: Code; + components?: Components; + variant?: string; + initialVariant?: string; + defaultVariant?: string; + precompute?: boolean | Code; + ContentLoading?: React.ComponentType; + ErrorHandler?: ErrorHandler; + clientOnly?: boolean; + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + fallbackUsesExtraFiles?: boolean; + fallbackUsesAllVariants?: boolean; + url?: string; + loadVariantCode?: (variantName: string, url?: string) => Promise; + loadSource?: (variantName: string, fileName: string, url?: string) => Promise; + parseSource?: (source: string) => Promise; +}; + +const DEFAULT_HIGHLIGHT_AT = 'stream'; + +function HighlightErrorHandler({ error }: { error: Error }) { + return
Error: {error.message}
; +} + +function isSourceLoaded( + code: { source?: string | HastNodes | { hastJson: string } }, + highlightAt?: string, +): boolean { + if (!code.source) { + return false; + } + + if (typeof code.source === 'string' && highlightAt === 'init') { + // TODO: handle 'stream' case + return false; + } + + // if it's a hast node or hastJson, we assume it's loaded + return true; +} + +async function CodeSourceLoader(props: CodeHighlighterInnerProps) { + const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; + + // TODO: if props.variant is provided, we should only load that variant + + const variantNames = Object.keys(props.components || props.code || {}); + const variantCodes = await Promise.all( + variantNames.map( + async (variant): Promise<{ error: Error } | { variant: string; code: VariantCode }> => { + let codeVariant = props.code?.[variant]; + if (!codeVariant) { + const loadVariantCode = props.loadVariantCode; + if (!loadVariantCode) { + return { + error: new Error( + '"loadVariantCode" function is required when filenames are not provided', + ), + }; + } + + try { + codeVariant = await loadVariantCode(variant, props.url); + } catch (error) { + return { + error: new Error( + `Failed to load variant code (variant: ${variant}, url: ${props.url}): ${JSON.stringify(error)}`, + ), + }; + } + } + + const filename = codeVariant.fileName; + let source = codeVariant.source; + if (!source) { + const loadSource = props.loadSource; + if (!loadSource) { + return { + error: new Error('"loadSource" function is required when source is not provided'), + }; + } + + try { + source = await loadSource(variant, filename, props.url); + codeVariant = { ...codeVariant, source }; + } catch (error) { + return { + error: new Error( + `Failed to load source code (variant: ${variant}, file: ${filename}, url: ${props.url}): ${JSON.stringify(error)}`, + ), + }; + } + } + + if (typeof source === 'string') { + const parseSource = props.parseSource; + if (!parseSource) { + return { + error: new Error( + '"parseSource" function is required when source is a string and highlightAt is "init"', + ), + }; + } + + try { + source = await parseSource(source); + codeVariant = { ...codeVariant, source }; + } catch (error) { + return { + error: new Error( + `Failed to parse source code (variant: ${variant}, file: ${filename}, url: ${props.url}): ${JSON.stringify(error)}`, + ), + }; + } + } + + // TODO: extraFiles handling + + return { + variant, + code: codeVariant, + }; + }, + ), + ); + + const code: Code = {}; + const errors: Error[] = []; + for (const variant of variantCodes) { + if ('error' in variant) { + errors.push(variant.error); + } else { + code[variant.variant] = variant.code; + } + } + + if (errors.length > 0) { + return ( + JSON.stringify(err)).join('\n ')}`) + } + /> + ); + } + + return
test
; +} + +function CodeHighlighterInner(props: CodeHighlighterInnerProps) { + const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; + + const variants = props.code; + const variantNames = Object.keys(props.components || variants || {}); + const allCodeVariantsLoaded = variantNames.every((variant) => { + const codeVariant = variants?.[variant]; + if (!codeVariant || !isSourceLoaded(codeVariant, props.highlightAt)) { + return false; + } + + const extraFiles = codeVariant.extraFiles; + if (!extraFiles) { + return true; + } + + return Object.keys(extraFiles).every((file) => { + const extraFile = extraFiles[file]; + if (!extraFile || !isSourceLoaded(extraFile, props.highlightAt)) { + return false; + } + + return true; + }); + }); + + if (!variants || !allCodeVariantsLoaded) { + if (props.clientOnly) { + return ( + + ); + } + + return ; + } + + return
test
; // TODO: to codehighlighterclient +} + +export function hastToJsx(hast: HastNodes): React.ReactNode { + return toJsxRuntime(hast, { Fragment, jsx, jsxs }); +} + +export function hastOrJsonToJsx(hastOrJson: HastNodes | { hastJson: string }): React.ReactNode { + let hast: HastNodes; + if ('hastJson' in hastOrJson) { + try { + hast = JSON.parse(hastOrJson.hastJson); + } catch (error) { + throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); + } + } else { + hast = hastOrJson; + } + + return toJsxRuntime(hast, { Fragment, jsx, jsxs }); +} + +function stringOrHastToJsx( + source: string | HastNodes | { hastJson: string }, + highlighted?: boolean, +): React.ReactNode { + if (typeof source === 'string') { + return
{source}
; + } + + let hast: HastNodes; + if ('hastJson' in source) { + try { + hast = JSON.parse(source.hastJson); + } catch (error) { + throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); + } + } else { + hast = source; + } + + if (highlighted) { + return
{hastToJsx(hast)}
; + } + + return
{toText(hast)}
; +} + +function CodeHighlighterWithInitialSource(props: CodeHighlighterWithInitialSourceProps) { + const fileNames = [props.initialFilename, ...Object.keys(props.initialExtraFiles || {})]; + const source = stringOrHastToJsx(props.initialSource, props.highlightAt === 'init'); + + const ContentLoading = props.ContentLoading; + const fallback = ; + + const innerProps: CodeHighlighterInnerProps = { + ...props, + fallback, + }; + delete (innerProps as any).ContentLoading; + delete (innerProps as any).initialVariant; + delete (innerProps as any).initialFilename; + delete (innerProps as any).initialSource; + delete (innerProps as any).initialExtraFiles; + delete (innerProps as any).fallbackUsesExtraFiles; + delete (innerProps as any).fallbackUsesAllVariants; + + if (props.clientOnly) { + return ; + } + + return ( + + {/* TODO: We need to wrap this in async so it always fallsback */} + + + ); +} + +async function CodeInitialSourceLoader(props: CodeInitialSourceLoaderProps) { + const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; + + const code = props.code || {}; + let initial = props.initial; + if (!initial) { + const loadVariantCode = props.loadVariantCode; + if (!loadVariantCode) { + return ( + + ); + } + + try { + initial = await loadVariantCode(props.initialVariant, props.url); + code[props.initialVariant] = initial; + } catch (error) { + return ( + + ); + } + } + + const initialFilename = initial.fileName; + let initialSource = initial.source; + if (!initialSource) { + const loadSource = props.loadSource; + if (!loadSource) { + return ( + + ); + } + + try { + initialSource = await loadSource(props.initialVariant, initialFilename, props.url); + code[props.initialVariant] = { ...(code[props.initialVariant] || {}), source: initialSource }; + } catch (error) { + return ( + + ); + } + } + + if (props.highlightAt === 'init' && typeof initialSource === 'string') { + const parseSource = props.parseSource; + if (!parseSource) { + return ( + + ); + } + + try { + initialSource = await parseSource(initialSource); + code[props.initialVariant] = { ...(code[props.initialVariant] || {}), source: initialSource }; + } catch (error) { + return ( + + ); + } + } + + // TODO: handle fallbackUsesExtraFiles and fallbackUsesAllVariants + + const propsWithInitialSource: CodeHighlighterWithInitialSourceProps = { + ...props, + code, + initialFilename, + initialSource, + initialExtraFiles: initial.extraFiles || {}, + }; + delete (propsWithInitialSource as any).initial; + + return ; +} + +function CodeHighlighter(props: CodeHighlighterProps) { + const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; + if (props.precompute === true) { + return ; + } + + const code = props.precompute || props.code; + const variants = Object.keys(props.components || code || {}); + if (variants.length === 0) { + return ; + } + + const ContentLoading = props.ContentLoading; + if (!ContentLoading) { + if (props.highlightAt === 'stream') { + // if the user explicitly sets highlightAt to 'stream', we need a ContentLoading component + return ( + + ); + } + + const innerProps: CodeHighlighterInnerProps = { + ...props, + code, + }; + delete (innerProps as any).ContentLoading; + delete (innerProps as any).precompute; + delete (innerProps as any).initialVariant; + delete (innerProps as any).fallbackUsesExtraFiles; + delete (innerProps as any).fallbackUsesAllVariants; + + return ; + } + + const initialKey = props.initialVariant || props.variant || props.defaultVariant || 'Default'; + const initial = code?.[initialKey]; + if (!initial && !props.components?.[initialKey]) { + return ; + } + + const initialSource = initial?.source; + const highlightAt = props.highlightAt || DEFAULT_HIGHLIGHT_AT; + + // TODO: handle fallbackUsesAllVariants and fallbackUsesExtraFiles + + if (!code || !initialSource || (highlightAt === 'init' && typeof initialSource === 'string')) { + if (props.clientOnly) { + if (!initialSource) { + return ( + + ); + } + + return ( + + ); + } + + return ( + + ); + } + + const propsWithInitialSource: CodeHighlighterWithInitialSourceProps = { + ...props, + code, + ContentLoading, + initialVariant: initialKey, + initialFilename: initial.fileName, + initialSource, + initialExtraFiles: initial.extraFiles, + }; + + return ; +} + +export default CodeHighlighter; diff --git a/packages/docs-infra/src/CodeHighlighter/CodeHighlighterClient.tsx b/packages/docs-infra/src/CodeHighlighter/CodeHighlighterClient.tsx new file mode 100644 index 000000000..04b1ce85b --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/CodeHighlighterClient.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { useHighlight } from '../HighlightProvider/HighlightContext'; + +function CodeHighlighterClient() { + const highlight = useHighlight(); // TODO: use to highlight on the client + + // handles on-hydration and idle switch +} + +export default CodeHighlighterClient; diff --git a/packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx b/packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx new file mode 100644 index 000000000..d87438886 --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx @@ -0,0 +1,18 @@ +import CodeHighlighter, { ContentProps, hastOrJsonToJsx } from '../../CodeHighlighter'; + +function CodeContent(props: ContentProps) { + return ( +
+

{props.code.Default.fileName}

+
{hastOrJsonToJsx(props.code.Default.source)}
+
+ ); +} + +function Code({ children, fileName = 'index.js' }: { children: string; fileName?: string }) { + return ( + + ); +} + +export default Code; diff --git a/packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx b/packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx new file mode 100644 index 000000000..b2e269569 --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import CodeHighlighter from '../../CodeHighlighter'; +import type { CodeHighlighterProps, Components, ContentProps } from '../../CodeHighlighter'; +import { hastOrJsonToJsx } from '../../CodeHighlighter'; + +function DemoContent(props: ContentProps) { + return ( +
+
{props.components?.Default}
+

{props.name}

+

{props.description}

+
{hastOrJsonToJsx(props.code?.Default?.source)}
+
+ ); +} + +type DemoProps = Pick; +type Demo = React.ComponentType; + +type Options = Pick; + +function createDemo( + url: string, + components: { [key: string]: React.ComponentType }, + opts: Options, +) { + function Component(props: DemoProps) { + const renderedComponents: Components = Object.entries(components).reduce( + (acc, [key, Variant]) => { + acc[key] = ; + return acc; + }, + {} as Components, + ); + + return ( + + ); + } + + if (process.env.NODE_ENV !== 'production') { + Component.displayName = `${opts.name}Demo`; // TODO: should have displayName instead + } + + return Component as Demo; +} + +const Demo = createDemo( + import.meta.url, + { + Default: () =>
Default Demo Component
, + }, + { + name: 'Demo', + slug: 'demo', + description: 'This is a demo component for CodeHighlighter.', + code: { + Default: { + fileName: 'index.js', + source: `() =>
Default Demo Component
`, + }, + }, + }, +); + +export default Demo; diff --git a/packages/docs-infra/src/CodeHighlighter/index.test.tsx b/packages/docs-infra/src/CodeHighlighter/index.test.tsx new file mode 100644 index 000000000..fe930f67a --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/index.test.tsx @@ -0,0 +1,70 @@ +import CodeHighlighter, { ContentProps } from './CodeHighlighter'; +import { hastOrJsonToJsx } from './CodeHighlighter'; + +function Content(props: ContentProps) { + return
{hastOrJsonToJsx(props.code.Default.source)}
; +} + +function Default() { + return
Default Component
; +} + +function A() { + // This is a simple case where we just pass the code without any precompute + return ( + Default Component' } }} + Content={Content} + /> + ); +} + +function B() { + return ( + // Single case optimized with precompute + Default Component' } }} + precompute={{ Default: { fileName: 'index.js', source: { hastJson: '{}' } } }} + Content={Content} + /> + ); +} + +function C() { + // A demo that will need to load the filenames and source code + return }} Content={Content} />; +} + +function D() { + // A demo that will need to highlight the code provided + return ( + }} + code={{ Default: { fileName: 'index.js', source: '
Default Component
' } }} + Content={Content} + /> + ); +} + +function E() { + // A demo with precompute optimization + return ( + }} + precompute={{ Default: { fileName: 'index.js', source: { hastJson: '{}' } } }} + Content={Content} + /> + ); +} + +function F() { + // A demo with code provided and precompute optimization + return ( + }} + code={{ Default: { fileName: 'index.js', source: '
Default Component
' } }} + precompute={{ Default: { fileName: 'index.js', source: { hastJson: '{}' } } }} + Content={Content} + /> + ); +} diff --git a/packages/docs-infra/src/CodeHighlighter/index.ts b/packages/docs-infra/src/CodeHighlighter/index.ts new file mode 100644 index 000000000..f4fea82c7 --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/index.ts @@ -0,0 +1,3 @@ +import CodeHighlighter from './CodeHighlighter'; + +export default CodeHighlighter; diff --git a/packages/docs-infra/src/HighlightProvider/HighlightContext.tsx b/packages/docs-infra/src/HighlightProvider/HighlightContext.tsx new file mode 100644 index 000000000..98ae39f61 --- /dev/null +++ b/packages/docs-infra/src/HighlightProvider/HighlightContext.tsx @@ -0,0 +1,16 @@ +// highlight context +import * as React from 'react'; +import type { Nodes as HastNode } from 'hast'; + +export const HighlightContext = React.createContext< + ((code: string, language: string) => Promise) | null +>(null); +export const useHighlight = () => { + const context = React.useContext(HighlightContext); + + if (!context) { + throw new Error('useHighlight must be used within a HighlightProvider'); + } + + return context; +}; diff --git a/packages/docs-infra/src/HighlightProvider/HighlightProvider.tsx b/packages/docs-infra/src/HighlightProvider/HighlightProvider.tsx new file mode 100644 index 000000000..b4cd008ce --- /dev/null +++ b/packages/docs-infra/src/HighlightProvider/HighlightProvider.tsx @@ -0,0 +1,22 @@ +'use client'; + +import * as React from 'react'; +import { common, createStarryNight } from '@wooorm/starry-night'; +import { HighlightContext } from './HighlightContext'; + +import type { Nodes as HastNode } from 'hast'; + +export function HighlightProvider({ children }: { children: React.ReactNode }) { + const [highlight, setHighlight] = React.useState< + ((code: string, language: string) => Promise) | null + >(null); + + React.useEffect(() => { + (async () => { + const starryNight = await createStarryNight(common); + setHighlight(async (code, language) => starryNight.highlight(code, language)); + })(); + }, []); + + return {children}; +} diff --git a/packages/docs-infra/src/HighlightProvider/index.ts b/packages/docs-infra/src/HighlightProvider/index.ts new file mode 100644 index 000000000..e089c6e6e --- /dev/null +++ b/packages/docs-infra/src/HighlightProvider/index.ts @@ -0,0 +1,3 @@ +import { HighlightProvider } from './HighlightProvider'; + +export default HighlightProvider; diff --git a/packages/docs-infra/tsconfig.json b/packages/docs-infra/tsconfig.json index ce043922b..767d4de4a 100644 --- a/packages/docs-infra/tsconfig.json +++ b/packages/docs-infra/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "jsx": "react-jsx", "skipLibCheck": true, "module": "es2020", "moduleResolution": "bundler", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af88b66d8..b02c5be1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -440,6 +440,9 @@ importers: '@types/hast': specifier: ^3.0.4 version: 3.0.4 + '@wooorm/starry-night': + specifier: ^3.8.0 + version: 3.8.0 clipboard-copy: specifier: ^4.0.1 version: 4.0.1 @@ -5317,6 +5320,9 @@ packages: resolution: {integrity: sha512-ueFCcIPaMgtuYDS9u0qlUoEvj6GiSsKrwnOLPp9SshqjtcRaR1IEHRjoReq3sXNydsF5i0ZnmuYgXq9dV53t0g==} engines: {node: '>=18.0.0'} + '@wooorm/starry-night@3.8.0': + resolution: {integrity: sha512-BWRm0tCzWCmv1ucBh6frL2uFRvYFj/LWmJzr+rJsdF/JsJ1+bBkeiyExH1iWQS18IH22HFu7f4QgG99OU1nklA==} + '@xhmikosr/archive-type@6.0.1': resolution: {integrity: sha512-PB3NeJL8xARZt52yDBupK0dNPn8uIVQDe15qNehUpoeeLWCZyAOam4vGXnoZGz2N9D1VXtjievJuCsXam2TmbQ==} engines: {node: ^14.14.0 || >=16.0.0} @@ -8099,6 +8105,9 @@ packages: engines: {node: '>=8'} hasBin: true + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -12049,6 +12058,12 @@ packages: vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + vscode-oniguruma@2.0.1: + resolution: {integrity: sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==} + + vscode-textmate@9.2.0: + resolution: {integrity: sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==} + wait-port@1.1.0: resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} engines: {node: '>=10'} @@ -18315,6 +18330,13 @@ snapshots: '@whatwg-node/promise-helpers': 1.3.1 tslib: 2.8.1 + '@wooorm/starry-night@3.8.0': + dependencies: + '@types/hast': 3.0.4 + import-meta-resolve: 4.1.0 + vscode-oniguruma: 2.0.1 + vscode-textmate: 9.2.0 + '@xhmikosr/archive-type@6.0.1': dependencies: file-type: 18.7.0 @@ -21693,6 +21715,8 @@ snapshots: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + import-meta-resolve@4.1.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -26191,6 +26215,10 @@ snapshots: vm-browserify@1.1.2: {} + vscode-oniguruma@2.0.1: {} + + vscode-textmate@9.2.0: {} + wait-port@1.1.0: dependencies: chalk: 4.1.2 From 464f786b0a6d09760129eae6be10ebdb912b7fe5 Mon Sep 17 00:00:00 2001 From: dav-is Date: Tue, 1 Jul 2025 19:25:01 -0400 Subject: [PATCH 002/210] Add first docs --- packages/docs-infra/docs/.gitignore | 41 ++ packages/docs-infra/docs/README.md | 3 + .../_mocks/checkbox/index.module.css | 22 + .../_mocks/checkbox/index.tsx | 11 + .../code-highlighter/demos}/Code.tsx | 5 +- .../code-highlighter/demos/code/BasicCode.tsx | 7 + .../code-highlighter/demos/code/index.ts | 52 ++ .../code-highlighter/demos}/createDemo.tsx | 31 +- .../demos/demo-variants/Demo.tsx | 26 + .../demos/demo/default/BasicCheckbox.tsx | 6 + .../demos/demo/default/index.tsx | 28 + .../code-highlighter/demos/demo/index.ts | 16 + .../app/components/code-highlighter/page.mdx | 5 + packages/docs-infra/docs/app/layout.tsx | 29 + packages/docs-infra/docs/eslint.config.mjs | 16 + packages/docs-infra/docs/mdx-components.tsx | 7 + packages/docs-infra/docs/next.config.ts | 10 + packages/docs-infra/docs/package.json | 30 + packages/docs-infra/docs/tsconfig.json | 27 + packages/docs-infra/package.json | 4 - .../docs-infra/src/CodeHighlighter/index.ts | 5 +- pnpm-lock.yaml | 665 +++++++++++++++++- pnpm-workspace.yaml | 1 + 23 files changed, 1002 insertions(+), 45 deletions(-) create mode 100644 packages/docs-infra/docs/.gitignore create mode 100644 packages/docs-infra/docs/README.md create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.module.css create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx rename packages/docs-infra/{src/CodeHighlighter/demos/code => docs/app/components/code-highlighter/demos}/Code.tsx (78%) create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code/BasicCode.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code/index.ts rename packages/docs-infra/{src/CodeHighlighter/demos/demo => docs/app/components/code-highlighter/demos}/createDemo.tsx (68%) create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/demo-variants/Demo.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/BasicCheckbox.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/index.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/demo/index.ts create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/page.mdx create mode 100644 packages/docs-infra/docs/app/layout.tsx create mode 100644 packages/docs-infra/docs/eslint.config.mjs create mode 100644 packages/docs-infra/docs/mdx-components.tsx create mode 100644 packages/docs-infra/docs/next.config.ts create mode 100644 packages/docs-infra/docs/package.json create mode 100644 packages/docs-infra/docs/tsconfig.json diff --git a/packages/docs-infra/docs/.gitignore b/packages/docs-infra/docs/.gitignore new file mode 100644 index 000000000..5ef6a5207 --- /dev/null +++ b/packages/docs-infra/docs/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/docs-infra/docs/README.md b/packages/docs-infra/docs/README.md new file mode 100644 index 000000000..4bb63f87f --- /dev/null +++ b/packages/docs-infra/docs/README.md @@ -0,0 +1,3 @@ +# MUI Docs-Infra Docs + +This package contains the documentation for the MUI Docs-Infra project, which is responsible for the infrastructure and tooling used in the MUI documentation site. diff --git a/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.module.css b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.module.css new file mode 100644 index 000000000..4e1943d61 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.module.css @@ -0,0 +1,22 @@ +.checkbox { + position: relative; + display: inline-block; + width: 20px; + height: 20px; +} + +.checkbox input { + opacity: 0; + width: 0; + height: 0; +} + +.checkbox .checkmark { + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 20px; + background-color: #eee; + border-radius: 4px; +} diff --git a/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx new file mode 100644 index 000000000..9a1163268 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import styles from './index.module.css'; + +export function Checkbox({ defaultChecked }: { defaultChecked?: boolean }) { + return ( + + ); +} diff --git a/packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/Code.tsx similarity index 78% rename from packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx rename to packages/docs-infra/docs/app/components/code-highlighter/demos/Code.tsx index d87438886..76cf043c4 100644 --- a/packages/docs-infra/src/CodeHighlighter/demos/code/Code.tsx +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/Code.tsx @@ -1,4 +1,7 @@ -import CodeHighlighter, { ContentProps, hastOrJsonToJsx } from '../../CodeHighlighter'; +import CodeHighlighter, { + ContentProps, + hastOrJsonToJsx, +} from '@mui/internal-docs-infra/CodeHighlighter/'; function CodeContent(props: ContentProps) { return ( diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/code/BasicCode.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/code/BasicCode.tsx new file mode 100644 index 000000000..f1e8701c2 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/code/BasicCode.tsx @@ -0,0 +1,7 @@ +import Code from '../Code'; + +function BasicCode() { + return {`console.log('Hello, world!');`}; +} + +export default BasicCode; diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/code/index.ts b/packages/docs-infra/docs/app/components/code-highlighter/demos/code/index.ts new file mode 100644 index 000000000..be4092a86 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/code/index.ts @@ -0,0 +1,52 @@ +import createDemo from '../createDemo'; +import BasicCode from './BasicCode'; + +export const CodeDemo = createDemo( + import.meta.url, + { Default: BasicCode }, // TODO: it would be nice to just do import.meta.url, BasicCode, { + { + name: 'Basic Code Block', + slug: 'code', + description: 'This shows a basic code block with syntax highlighting.', + // precompute: true, TODO: enable this + code: { + Default: { + fileName: 'BasicCode.tsx', + source: `import Code from '../Code'; + +function BasicCode() { + return {\`console.log('Hello, world!');\`}; +} + +export default BasicCode;`, + extraFiles: { + Code: { + fileName: '../Code.tsx', + source: `import CodeHighlighter, { + ContentProps, + hastOrJsonToJsx, +} from '../../../../../src/CodeHighlighter/CodeHighlighter'; + +function CodeContent(props: ContentProps) { + return ( +
+

{props.code.Default.fileName}

+
{hastOrJsonToJsx(props.code.Default.source)}
+
+ ); +} + +function Code({ children, fileName = 'index.js' }: { children: string; fileName?: string }) { + return ( + + ); +} + +export default Code; +`, + }, + }, + }, + }, + }, +); diff --git a/packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/createDemo.tsx similarity index 68% rename from packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx rename to packages/docs-infra/docs/app/components/code-highlighter/demos/createDemo.tsx index b2e269569..80a0e990b 100644 --- a/packages/docs-infra/src/CodeHighlighter/demos/demo/createDemo.tsx +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/createDemo.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; -import CodeHighlighter from '../../CodeHighlighter'; -import type { CodeHighlighterProps, Components, ContentProps } from '../../CodeHighlighter'; -import { hastOrJsonToJsx } from '../../CodeHighlighter'; +import CodeHighlighter from '@mui/internal-docs-infra/CodeHighlighter/'; +import { hastOrJsonToJsx } from '@mui/internal-docs-infra/CodeHighlighter/'; + +import type { + CodeHighlighterProps, + Components, + ContentProps, +} from '@mui/internal-docs-infra/CodeHighlighter/'; function DemoContent(props: ContentProps) { return ( @@ -51,22 +56,4 @@ function createDemo( return Component as Demo; } -const Demo = createDemo( - import.meta.url, - { - Default: () =>
Default Demo Component
, - }, - { - name: 'Demo', - slug: 'demo', - description: 'This is a demo component for CodeHighlighter.', - code: { - Default: { - fileName: 'index.js', - source: `() =>
Default Demo Component
`, - }, - }, - }, -); - -export default Demo; +export default createDemo; diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/demo-variants/Demo.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo-variants/Demo.tsx new file mode 100644 index 000000000..d675545b8 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo-variants/Demo.tsx @@ -0,0 +1,26 @@ +import createDemo from '../createDemo'; + +const Demo = createDemo( + import.meta.url, + { + Jsx: () =>
JSX Demo Component
, + Mdx: () =>
MDX Demo Component
, + }, + { + name: 'Demo', + slug: 'demo', + description: 'This is a demo component for CodeHighlighter.', + code: { + Jsx: { + fileName: 'index.js', + source: `() =>
JSX Demo Component
`, + }, + Mdx: { + fileName: 'index.js', + source: `() =>
MDX Demo Component
`, + }, + }, + }, +); + +export default Demo; diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/BasicCheckbox.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/BasicCheckbox.tsx new file mode 100644 index 000000000..a6d87f05a --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/BasicCheckbox.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { Checkbox } from '../../../_mocks/checkbox'; + +export function BasicCheckbox() { + return ; +} diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/index.tsx b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/index.tsx new file mode 100644 index 000000000..7373328b1 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/default/index.tsx @@ -0,0 +1,28 @@ +import createDemo from '../../createDemo'; +import { BasicCheckbox } from './BasicCheckbox'; + +const CheckboxDemo = createDemo( + import.meta.url, + { + Default: BasicCheckbox, + }, + { + name: 'Checkbox', + slug: 'basic', + description: 'This shows a basic checkbox component.', + code: { + Default: { + fileName: 'BasicCheckbox.js', + source: `import * as React from 'react'; +import { Checkbox } from '../../../_stubs/checkbox'; + +export function BasicCheckbox() { + return ; +} +`, // TODO: use precompute instead + }, + }, + }, +); + +export default CheckboxDemo; diff --git a/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/index.ts b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/index.ts new file mode 100644 index 000000000..834f97856 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/demos/demo/index.ts @@ -0,0 +1,16 @@ +import createDemo from '../createDemo'; +import { default as Default } from './default'; + +const DemoDemo = createDemo( + import.meta.url, + { Default }, + { + name: 'Demo', + slug: 'demo', + description: 'This is a demo component for CodeHighlighter.', + // pathPrefix: 'demos/basic', TODO: filename shown is prefixed with this + precompute: true, + }, +); + +export default DemoDemo; diff --git a/packages/docs-infra/docs/app/components/code-highlighter/page.mdx b/packages/docs-infra/docs/app/components/code-highlighter/page.mdx new file mode 100644 index 000000000..c5343224a --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-highlighter/page.mdx @@ -0,0 +1,5 @@ +# Code Highlighter + +import { CodeDemo } from './demos/code' + + diff --git a/packages/docs-infra/docs/app/layout.tsx b/packages/docs-infra/docs/app/layout.tsx new file mode 100644 index 000000000..5dbd3ef9d --- /dev/null +++ b/packages/docs-infra/docs/app/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from 'next'; +import { Geist, Geist_Mono } from 'next/font/google'; + +const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'], +}); + +const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'], +}); + +export const metadata: Metadata = { + title: 'MUI Docs-Infra Documentation', + description: 'How to use the MUI Docs-Infra package', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/packages/docs-infra/docs/eslint.config.mjs b/packages/docs-infra/docs/eslint.config.mjs new file mode 100644 index 000000000..c85fb67c4 --- /dev/null +++ b/packages/docs-infra/docs/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/packages/docs-infra/docs/mdx-components.tsx b/packages/docs-infra/docs/mdx-components.tsx new file mode 100644 index 000000000..380b0375a --- /dev/null +++ b/packages/docs-infra/docs/mdx-components.tsx @@ -0,0 +1,7 @@ +import type { MDXComponents } from 'mdx/types'; + +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { + ...components, + }; +} diff --git a/packages/docs-infra/docs/next.config.ts b/packages/docs-infra/docs/next.config.ts new file mode 100644 index 000000000..d57920272 --- /dev/null +++ b/packages/docs-infra/docs/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from 'next'; +import createMDX from '@next/mdx'; + +const nextConfig: NextConfig = { + pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], +}; + +const withMDX = createMDX({}); + +export default withMDX(nextConfig); diff --git a/packages/docs-infra/docs/package.json b/packages/docs-infra/docs/package.json new file mode 100644 index 000000000..9bab66211 --- /dev/null +++ b/packages/docs-infra/docs/package.json @@ -0,0 +1,30 @@ +{ + "name": "mui-docs-infra-docs", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@mdx-js/loader": "^3.1.0", + "@mdx-js/react": "^3.1.0", + "@next/mdx": "^15.3.4", + "@types/mdx": "^2.0.13", + "@mui/internal-docs-infra": "workspace:^", + "next": "15.3.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.3.4", + "typescript": "^5" + } +} diff --git a/packages/docs-infra/docs/tsconfig.json b/packages/docs-infra/docs/tsconfig.json new file mode 100644 index 000000000..d8b93235f --- /dev/null +++ b/packages/docs-infra/docs/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/docs-infra/package.json b/packages/docs-infra/package.json index 7277d61ef..31f4d13ee 100644 --- a/packages/docs-infra/package.json +++ b/packages/docs-infra/package.json @@ -4,10 +4,6 @@ "author": "MUI Team", "description": "MUI Infra - internal documentation creation tools.", "main": "./index.js", - "exports": { - ".": "./index.js", - "./*": "./*/index.js" - }, "keywords": [ "react", "react-component", diff --git a/packages/docs-infra/src/CodeHighlighter/index.ts b/packages/docs-infra/src/CodeHighlighter/index.ts index f4fea82c7..987a08576 100644 --- a/packages/docs-infra/src/CodeHighlighter/index.ts +++ b/packages/docs-infra/src/CodeHighlighter/index.ts @@ -1,3 +1,2 @@ -import CodeHighlighter from './CodeHighlighter'; - -export default CodeHighlighter; +export * from './CodeHighlighter'; +export { default } from './CodeHighlighter'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b02c5be1e..d0f5f5ce4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -524,6 +524,55 @@ importers: version: 17.7.2 publishDirectory: build + packages/docs-infra/docs: + dependencies: + '@mdx-js/loader': + specifier: ^3.1.0 + version: 3.1.0(acorn@8.15.0)(webpack@5.94.0) + '@mdx-js/react': + specifier: ^3.1.0 + version: 3.1.0(@types/react@19.1.8)(react@19.1.0) + '@mui/internal-docs-infra': + specifier: workspace:^ + version: link:../build + '@next/mdx': + specifier: ^15.3.4 + version: 15.3.4(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.94.0))(@mdx-js/react@3.1.0(@types/react@19.1.8)(react@19.1.0)) + '@types/mdx': + specifier: ^2.0.13 + version: 2.0.13 + next: + specifier: 15.3.4 + version: 15.3.4(@babel/core@7.27.4)(@opentelemetry/api@1.8.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@types/node': + specifier: ^20 + version: 20.19.1 + '@types/react': + specifier: ^19 + version: 19.1.8 + '@types/react-dom': + specifier: ^19 + version: 19.1.2(@types/react@19.1.8) + eslint: + specifier: ^9 + version: 9.29.0(jiti@2.4.2) + eslint-config-next: + specifier: 15.3.4 + version: 15.3.4(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + typescript: + specifier: ^5 + version: 5.8.3 + test/bundle-size: dependencies: '@base-ui-components/react': @@ -2368,6 +2417,23 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true + '@mdx-js/loader@3.1.0': + resolution: {integrity: sha512-xU/lwKdOyfXtQGqn3VnJjlDrmKXEvMi1mgYxVmukEUtVycIz1nh7oQ40bKTd4cA7rLStqu0740pnhGYxGoqsCg==} + peerDependencies: + webpack: '>=5' + peerDependenciesMeta: + webpack: + optional: true + + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} + + '@mdx-js/react@3.1.0': + resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@molt/command@0.9.0': resolution: {integrity: sha512-1JI8dAlpqlZoXyKWVQggX7geFNPxBpocHIXQCsnxDjKy+3WX4SGyZVJXuLlqRRrX7FmQCuuMAfx642ovXmPA9g==} @@ -3079,6 +3145,17 @@ packages: '@next/eslint-plugin-next@15.3.4': resolution: {integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==} + '@next/mdx@15.3.4': + resolution: {integrity: sha512-Ok4Laq+Yxxu0hPefpE7Yi19dj8BBTIw9/Kf0fbRByn2sYF1cAINFG1EcfcZUy6tZ5ctB8jEtjzixUsKXvFuRXA==} + peerDependencies: + '@mdx-js/loader': '>=0.15.0' + '@mdx-js/react': '>=0.15.0' + peerDependenciesMeta: + '@mdx-js/loader': + optional: true + '@mdx-js/react': + optional: true + '@next/swc-darwin-arm64@15.3.4': resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==} engines: {node: '>= 10'} @@ -3142,6 +3219,10 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + '@npmcli/agent@2.2.2': resolution: {integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==} engines: {node: ^16.14.0 || >=18.0.0} @@ -4344,6 +4425,9 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@rushstack/eslint-patch@1.12.0': + resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -4850,6 +4934,9 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/micromatch@4.0.9': resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==} @@ -5117,6 +5204,9 @@ packages: resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/resolver-binding-android-arm-eabi@1.9.2': resolution: {integrity: sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==} cpu: [arm] @@ -5657,6 +5747,10 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} @@ -5757,6 +5851,9 @@ packages: resolution: {integrity: sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==} engines: {node: '>= 0.6'} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -6144,6 +6241,9 @@ packages: resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -6973,6 +7073,12 @@ packages: resolution: {integrity: sha512-aiQ/QyJBVJbabtsSediM1S4qI+P3p8F5J5YR5o/bH003BCnnclzxK9pi5Qd2Hg01ktAtZCaQBdejHrkOBGwf5Q==} engines: {node: '>=0.4.0'} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild@0.19.11: resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} engines: {node: '>=12'} @@ -7038,6 +7144,15 @@ packages: eslint-plugin-react: ^7.28.0 eslint-plugin-react-hooks: ^4.3.0 + eslint-config-next@15.3.4: + resolution: {integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + eslint-config-prettier@10.1.5: resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==} hasBin: true @@ -7056,6 +7171,19 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + eslint-import-resolver-typescript@4.4.4: resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} engines: {node: ^16.17.0 || >=18.6.0} @@ -7117,6 +7245,12 @@ packages: peerDependencies: eslint: '>=7' + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@6.0.0: resolution: {integrity: sha512-NyC3yIC9fazLitYiN8eHykV5wLp/SMuUZMh+sdPSHIeN4ReXIc7if40jtGjDplAgVL/4OkN1d9gneWe9lFZgag==} engines: {node: '>=18'} @@ -7187,9 +7321,24 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -7942,6 +8091,9 @@ packages: hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} @@ -8977,6 +9129,10 @@ packages: resolution: {integrity: sha512-K6K2NgKnTXimT3779/4KxSvobxOtMmx1LBZ3NwRxT/MDIR3Br/fQ4Q+WCX5QxjyUR8zg5+RV9Tbf2c5pAWTD2A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + markdown-to-jsx@7.7.0: resolution: {integrity: sha512-130nIMbJY+woOQJ11xTqEtYko60t6EpNkZuqjKMferL3udtob3nRfzXOdsiA26NPemiR7w/hR8M3/B9yiYPGZg==} engines: {node: '>= 10'} @@ -9002,12 +9158,18 @@ packages: mdast-util-mdx-jsx@3.2.0: resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + mdast-util-mdxjs-esm@2.0.1: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -9062,12 +9224,30 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} @@ -9098,6 +9278,9 @@ packages: micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} @@ -10570,6 +10753,18 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.0: + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + recursive-readdir@2.2.3: resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} engines: {node: '>=6.0.0'} @@ -10612,10 +10807,22 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + rejoinder@2.0.1: resolution: {integrity: sha512-HDS/V9m4bOqtuLp26egtNNjmHuDg5Ei21cPgRwOX0EM/Sa6EOqSyU79bRcQ2In2VhkrG+eNMZ54uyMn9L6YF6Q==} engines: {node: ^20.18.0 || ^22.12.0 || >=23.3.0} + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + remeda@1.61.0: resolution: {integrity: sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==} @@ -11079,6 +11286,9 @@ packages: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} @@ -11485,6 +11695,9 @@ packages: resolution: {integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -11497,6 +11710,9 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -11720,6 +11936,9 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -11738,6 +11957,9 @@ packages: unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -11943,6 +12165,9 @@ packages: vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.1.3: resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -14457,7 +14682,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/yargs': 16.0.9 chalk: 4.1.2 @@ -14602,6 +14827,52 @@ snapshots: - encoding - supports-color + '@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.94.0)': + dependencies: + '@mdx-js/mdx': 3.1.0(acorn@8.15.0) + source-map: 0.7.4 + optionalDependencies: + webpack: 5.94.0 + transitivePeerDependencies: + - acorn + - supports-color + + '@mdx-js/mdx@3.1.0(acorn@8.15.0)': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.4 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - acorn + - supports-color + + '@mdx-js/react@3.1.0(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.1.8 + react: 19.1.0 + '@molt/command@0.9.0': dependencies: '@molt/types': 0.2.0 @@ -15544,6 +15815,13 @@ snapshots: dependencies: fast-glob: 3.3.1 + '@next/mdx@15.3.4(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.94.0))(@mdx-js/react@3.1.0(@types/react@19.1.8)(react@19.1.0))': + dependencies: + source-map: 0.7.4 + optionalDependencies: + '@mdx-js/loader': 3.1.0(acorn@8.15.0)(webpack@5.94.0) + '@mdx-js/react': 3.1.0(@types/react@19.1.8)(react@19.1.0) + '@next/swc-darwin-arm64@15.3.4': optional: true @@ -15583,6 +15861,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@nolyfill/is-core-module@1.0.39': {} + '@npmcli/agent@2.2.2': dependencies: agent-base: 7.1.3 @@ -16846,6 +17126,8 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@rushstack/eslint-patch@1.12.0': {} + '@sec-ant/readable-stream@0.4.1': {} '@sigstore/bundle@2.3.2': @@ -17544,7 +17826,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/braces@3.0.5': {} @@ -17552,23 +17834,23 @@ snapshots: '@types/clean-css@4.2.11': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 source-map: 0.6.1 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.5 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/connect@3.4.38': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/cookie@0.6.0': {} '@types/cors@2.8.17': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/d3-color@3.1.3': {} @@ -17617,7 +17899,7 @@ snapshots: '@types/express-serve-static-core@4.19.5': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -17659,7 +17941,7 @@ snapshots: '@types/http-proxy@1.17.15': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -17679,6 +17961,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdx@2.0.13': {} + '@types/micromatch@4.0.9': dependencies: '@types/braces': 3.0.5 @@ -17724,7 +18008,7 @@ snapshots: '@types/eslint': eslint@9.29.0(jiti@2.4.2) '@types/express': 4.17.21 '@types/html-webpack-plugin': 3.2.9 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/webpack': 4.41.39 '@types/webpack-dev-server': 3.11.6 transitivePeerDependencies: @@ -17755,12 +18039,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/send': 0.17.4 '@types/source-list-map@0.1.6': {} @@ -17804,13 +18088,13 @@ snapshots: '@types/webpack-sources@3.2.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/source-list-map': 0.1.6 source-map: 0.7.4 '@types/webpack@4.41.39': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 '@types/tapable': 1.0.12 '@types/uglify-js': 3.17.5 '@types/webpack-sources': 3.2.3 @@ -17840,7 +18124,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 optional: true '@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': @@ -17861,6 +18145,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/type-utils': 7.12.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 7.12.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 7.12.0 + eslint: 9.29.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -18065,6 +18367,8 @@ snapshots: '@typescript-eslint/types': 8.35.0 eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.9.2': optional: true @@ -18736,6 +19040,8 @@ snapshots: ast-types-flow@0.0.8: {} + astring@1.9.0: {} + async-sema@3.1.1: {} async@3.2.6: {} @@ -18861,6 +19167,8 @@ snapshots: dependencies: precond: 0.2.3 + bail@2.0.2: {} + balanced-match@1.0.2: {} bare-events@2.5.4: @@ -19258,6 +19566,8 @@ snapshots: cmd-shim@6.0.3: {} + collapse-white-space@2.1.0: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -20162,6 +20472,20 @@ snapshots: string.prototype.trimleft: 2.1.3 string.prototype.trimright: 2.1.3 + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 + esbuild@0.19.11: optionalDependencies: '@esbuild/aix-ppc64': 0.19.11 @@ -20310,6 +20634,26 @@ snapshots: object.assign: 4.1.7 object.entries: 1.1.9 + eslint-config-next@15.3.4(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@next/eslint-plugin-next': 15.3.4 + '@rushstack/eslint-patch': 1.12.0 + '@typescript-eslint/eslint-plugin': 7.12.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.29.0(jiti@2.4.2)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)): dependencies: eslint: 9.29.0(jiti@2.4.2) @@ -20329,6 +20673,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.29.0(jiti@2.4.2) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.14 + unrs-resolver: 1.9.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.4.2)): dependencies: debug: 4.4.1(supports-color@8.1.1) @@ -20344,6 +20703,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2)): dependencies: debug: 3.2.7 @@ -20355,6 +20725,35 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 @@ -20421,6 +20820,10 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-react-hooks@5.2.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + eslint-plugin-react-hooks@6.0.0(eslint@9.29.0(jiti@2.4.2)): dependencies: '@babel/core': 7.27.4 @@ -20540,8 +20943,35 @@ snapshots: estraverse@5.3.0: {} + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + estree-util-is-identifier-name@3.0.0: {} + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.4 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -21498,6 +21928,27 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.17 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 @@ -22145,7 +22596,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.14.0 + '@types/node': 20.19.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -22686,6 +23137,8 @@ snapshots: map-obj@5.0.2: {} + markdown-extensions@2.0.0: {} + markdown-to-jsx@7.7.0(react@19.1.0): dependencies: react: 19.1.0 @@ -22746,6 +23199,16 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + mdast-util-mdxjs-esm@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 @@ -22762,6 +23225,18 @@ snapshots: '@types/mdast': 4.0.4 unist-util-is: 6.0.0 + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 @@ -22839,6 +23314,57 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -22852,6 +23378,18 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + micromark-factory-space@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -22904,6 +23442,16 @@ snapshots: micromark-util-encode@2.0.1: {} + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + micromark-util-html-tag-name@2.0.1: {} micromark-util-normalize-identifier@2.0.1: @@ -24683,6 +25231,36 @@ snapshots: real-require@0.2.0: {} + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.0(acorn@8.15.0): + dependencies: + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + recursive-readdir@2.2.3: dependencies: minimatch: 3.1.2 @@ -24741,12 +25319,44 @@ snapshots: dependencies: jsesc: 3.0.2 + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + rejoinder@2.0.1: dependencies: '@-xun/debug': 2.0.1 chalk: 5.4.1 core-js: 3.43.0 + 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 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + remeda@1.61.0: {} remove-trailing-separator@1.1.0: {} @@ -25257,6 +25867,8 @@ snapshots: stable-hash-x@0.2.0: {} + stable-hash@0.0.5: {} + stack-generator@2.0.10: dependencies: stackframe: 1.3.4 @@ -25686,6 +26298,8 @@ snapshots: treeverse@3.0.0: {} + trim-lines@3.0.1: {} + trim-newlines@3.0.1: {} trim-repeated@2.0.0: @@ -25694,6 +26308,8 @@ snapshots: triple-beam@1.4.1: {} + trough@2.2.0: {} + ts-api-utils@1.3.0(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -25899,6 +26515,16 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -25920,6 +26546,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-position@5.0.0: dependencies: '@types/unist': 3.0.3 @@ -26099,6 +26729,11 @@ snapshots: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + vite-node@3.1.3(@types/node@24.0.3)(jiti@2.4.2)(terser@5.39.0)(tsx@4.20.3)(yaml@2.7.1): dependencies: cac: 6.7.14 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a24296216..b13238785 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,7 @@ packages: - 'packages/*' - 'apps/*' - 'test/*' + - 'packages/docs-infra/docs' overrides: # This is a workaround for the issue when the same type refers to @types/eslint for one instance # and to eslint for another instance, causing a conflict. From 4f469879f3b5b0926bac48b6d5b549993ed9236c Mon Sep 17 00:00:00 2001 From: dav-is Date: Wed, 2 Jul 2025 16:05:23 -0400 Subject: [PATCH 003/210] Refactor CodeHighlighter --- .../src/CodeHighlighter/CodeHighlighter.tsx | 413 +++--------------- .../docs-infra/src/CodeHighlighter/hast.tsx | 50 +++ .../src/CodeHighlighter/index.test.tsx | 5 +- .../CodeHighlighter/loadFallbackVariant.ts | 93 ++++ .../src/CodeHighlighter/loadVariant.ts | 65 +++ .../docs-infra/src/CodeHighlighter/types.ts | 63 +++ 6 files changed, 337 insertions(+), 352 deletions(-) create mode 100644 packages/docs-infra/src/CodeHighlighter/hast.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/loadFallbackVariant.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/loadVariant.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/types.ts diff --git a/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx b/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx index 2a08944a5..8d34afed7 100644 --- a/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx +++ b/packages/docs-infra/src/CodeHighlighter/CodeHighlighter.tsx @@ -1,125 +1,36 @@ +import type { + Code, + CodeHighlighterProps, + ContentLoadingProps, + Fallback, + VariantCode, + VariantExtraFiles, + VariantSource, +} from './types'; + import * as React from 'react'; -import { Fragment, jsx, jsxs } from 'react/jsx-runtime'; -import { toText } from 'hast-util-to-text'; -import { toJsxRuntime } from 'hast-util-to-jsx-runtime'; - -import type { Nodes as HastNodes } from 'hast'; - -export type Components = { [key: string]: React.ReactNode }; - -type CodeMeta = { - fileName: string; -}; -type VariantExtraFiles = { - [fileName: string]: null | (CodeMeta & { source?: string | HastNodes | { hastJson: string } }); -}; -type VariantCode = CodeMeta & { - source?: string | HastNodes | { hastJson: string }; - extraFiles?: VariantExtraFiles; -}; -type Code = { [key: string]: VariantCode }; -type ParsedVariantCode = CodeMeta & { - source: HastNodes | { hastJson: string }; - extraFiles: { [fileName: string]: CodeMeta & { source: HastNodes | { hastJson: string } } }; -}; -type ParsedCode = { [key: string]: ParsedVariantCode }; - -type Options = { name?: string; slug?: string; description?: string }; -export type ContentProps = { code: ParsedCode; components?: Components } & Options; -export type ContentLoadingProps = { fileNames: string[]; source: React.ReactNode }; - -type ErrorHandler = React.ComponentType<{ error: Error }>; - -export type CodeHighlighterClientProps = Options & { - Content: React.ComponentType; - code?: Code; - components?: Components; - variant?: string; - defaultVariant?: string; - ErrorHandler?: ErrorHandler; - highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; - url?: string; - fallback?: React.ReactNode; -}; - -export type CodeHighlighterInnerProps = Options & { - Content: React.ComponentType; - code?: Code; - components?: Components; - variant?: string; - defaultVariant?: string; - ErrorHandler?: ErrorHandler; - clientOnly?: boolean; - highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; - url?: string; - fallback?: React.ReactNode; - loadVariantCode?: (variantName: string, url?: string) => Promise; - loadSource?: (variantName: string, fileName: string, url?: string) => Promise; - parseSource?: (source: string) => Promise; -}; - -type CodeHighlighterWithInitialSourceProps = Options & { - Content: React.ComponentType; - code: Code; // e - initialVariant: string; // e - initialFilename: string; // e - initialSource: string | HastNodes | { hastJson: string }; // e - initialExtraFiles?: VariantExtraFiles; // e - components?: Components; - variant?: string; - defaultVariant?: string; - ContentLoading: React.ComponentType; // e - ErrorHandler?: ErrorHandler; - clientOnly?: boolean; - highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; - fallbackUsesExtraFiles?: boolean; - fallbackUsesAllVariants?: boolean; - url?: string; - loadVariantCode?: (variantName: string, url?: string) => Promise; - loadSource?: (variantName: string, fileName: string, url?: string) => Promise; - parseSource?: (source: string) => Promise; -}; - -type CodeInitialSourceLoaderProps = Options & { - Content: React.ComponentType; - code?: Code; - components?: Components; - variant?: string; - initialVariant: string; // e - initial?: VariantCode; // e - defaultVariant?: string; - precompute?: boolean | Code; +import { loadVariant } from './loadVariant'; +import { loadFallbackVariant } from './loadFallbackVariant'; +import { stringOrHastToJsx } from './hast'; + +interface CodeHighlighterInnerProps extends Omit { + fallback?: Fallback; +} + +interface CodeHighlighterWithInitialSourceProps extends Omit { + code: Code; + initialVariant: string; + initialFilename: string; + initialSource: VariantSource; + initialExtraFiles?: VariantExtraFiles; ContentLoading: React.ComponentType; - ErrorHandler?: ErrorHandler; - clientOnly?: boolean; - highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; - fallbackUsesExtraFiles?: boolean; - fallbackUsesAllVariants?: boolean; - url?: string; - loadVariantCode?: (variantName: string, url?: string) => Promise; - loadSource?: (variantName: string, fileName: string, url?: string) => Promise; - parseSource?: (source: string) => Promise; -}; - -export type CodeHighlighterProps = Options & { - Content: React.ComponentType; - code?: Code; - components?: Components; - variant?: string; - initialVariant?: string; - defaultVariant?: string; - precompute?: boolean | Code; - ContentLoading?: React.ComponentType; - ErrorHandler?: ErrorHandler; - clientOnly?: boolean; - highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; - fallbackUsesExtraFiles?: boolean; - fallbackUsesAllVariants?: boolean; - url?: string; - loadVariantCode?: (variantName: string, url?: string) => Promise; - loadSource?: (variantName: string, fileName: string, url?: string) => Promise; - parseSource?: (source: string) => Promise; -}; +} + +interface CodeInitialSourceLoaderProps extends Omit { + initialVariant: string; + initial?: VariantCode; + ContentLoading: React.ComponentType; +} const DEFAULT_HIGHLIGHT_AT = 'stream'; @@ -127,10 +38,7 @@ function HighlightErrorHandler({ error }: { error: Error }) { return
Error: {error.message}
; } -function isSourceLoaded( - code: { source?: string | HastNodes | { hastJson: string } }, - highlightAt?: string, -): boolean { +function isSourceLoaded(code: { source?: VariantSource }, highlightAt?: string): boolean { if (!code.source) { return false; } @@ -151,81 +59,8 @@ async function CodeSourceLoader(props: CodeHighlighterInnerProps) { const variantNames = Object.keys(props.components || props.code || {}); const variantCodes = await Promise.all( - variantNames.map( - async (variant): Promise<{ error: Error } | { variant: string; code: VariantCode }> => { - let codeVariant = props.code?.[variant]; - if (!codeVariant) { - const loadVariantCode = props.loadVariantCode; - if (!loadVariantCode) { - return { - error: new Error( - '"loadVariantCode" function is required when filenames are not provided', - ), - }; - } - - try { - codeVariant = await loadVariantCode(variant, props.url); - } catch (error) { - return { - error: new Error( - `Failed to load variant code (variant: ${variant}, url: ${props.url}): ${JSON.stringify(error)}`, - ), - }; - } - } - - const filename = codeVariant.fileName; - let source = codeVariant.source; - if (!source) { - const loadSource = props.loadSource; - if (!loadSource) { - return { - error: new Error('"loadSource" function is required when source is not provided'), - }; - } - - try { - source = await loadSource(variant, filename, props.url); - codeVariant = { ...codeVariant, source }; - } catch (error) { - return { - error: new Error( - `Failed to load source code (variant: ${variant}, file: ${filename}, url: ${props.url}): ${JSON.stringify(error)}`, - ), - }; - } - } - - if (typeof source === 'string') { - const parseSource = props.parseSource; - if (!parseSource) { - return { - error: new Error( - '"parseSource" function is required when source is a string and highlightAt is "init"', - ), - }; - } - - try { - source = await parseSource(source); - codeVariant = { ...codeVariant, source }; - } catch (error) { - return { - error: new Error( - `Failed to parse source code (variant: ${variant}, file: ${filename}, url: ${props.url}): ${JSON.stringify(error)}`, - ), - }; - } - } - - // TODO: extraFiles handling - - return { - variant, - code: codeVariant, - }; - }, + variantNames.map((variantName) => + loadVariant(variantName, props.url, props.code?.[variantName]).catch((error) => ({ error })), ), ); @@ -291,49 +126,11 @@ function CodeHighlighterInner(props: CodeHighlighterInnerProps) { return
test
; // TODO: to codehighlighterclient } -export function hastToJsx(hast: HastNodes): React.ReactNode { - return toJsxRuntime(hast, { Fragment, jsx, jsxs }); -} - -export function hastOrJsonToJsx(hastOrJson: HastNodes | { hastJson: string }): React.ReactNode { - let hast: HastNodes; - if ('hastJson' in hastOrJson) { - try { - hast = JSON.parse(hastOrJson.hastJson); - } catch (error) { - throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); - } - } else { - hast = hastOrJson; - } - - return toJsxRuntime(hast, { Fragment, jsx, jsxs }); -} - -function stringOrHastToJsx( - source: string | HastNodes | { hastJson: string }, - highlighted?: boolean, -): React.ReactNode { - if (typeof source === 'string') { - return
{source}
; - } - - let hast: HastNodes; - if ('hastJson' in source) { - try { - hast = JSON.parse(source.hastJson); - } catch (error) { - throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); - } - } else { - hast = source; - } - - if (highlighted) { - return
{hastToJsx(hast)}
; - } - - return
{toText(hast)}
; +/** + * Ensures that the suspense boundary is always rendered, even if none of the children have async operations. + */ +async function CodeHighlighterSuspense(props: { children: React.ReactNode }) { + return props.children; } function CodeHighlighterWithInitialSource(props: CodeHighlighterWithInitialSourceProps) { @@ -347,13 +144,9 @@ function CodeHighlighterWithInitialSource(props: CodeHighlighterWithInitialSourc ...props, fallback, }; - delete (innerProps as any).ContentLoading; - delete (innerProps as any).initialVariant; delete (innerProps as any).initialFilename; delete (innerProps as any).initialSource; delete (innerProps as any).initialExtraFiles; - delete (innerProps as any).fallbackUsesExtraFiles; - delete (innerProps as any).fallbackUsesAllVariants; if (props.clientOnly) { return ; @@ -361,8 +154,9 @@ function CodeHighlighterWithInitialSource(props: CodeHighlighterWithInitialSourc return ( - {/* TODO: We need to wrap this in async so it always fallsback */} - + + + ); } @@ -370,104 +164,24 @@ function CodeHighlighterWithInitialSource(props: CodeHighlighterWithInitialSourc async function CodeInitialSourceLoader(props: CodeInitialSourceLoaderProps) { const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; - const code = props.code || {}; - let initial = props.initial; - if (!initial) { - const loadVariantCode = props.loadVariantCode; - if (!loadVariantCode) { - return ( - - ); - } - - try { - initial = await loadVariantCode(props.initialVariant, props.url); - code[props.initialVariant] = initial; - } catch (error) { - return ( - - ); - } - } - - const initialFilename = initial.fileName; - let initialSource = initial.source; - if (!initialSource) { - const loadSource = props.loadSource; - if (!loadSource) { - return ( - - ); - } - - try { - initialSource = await loadSource(props.initialVariant, initialFilename, props.url); - code[props.initialVariant] = { ...(code[props.initialVariant] || {}), source: initialSource }; - } catch (error) { - return ( - - ); - } + const loaded = await loadFallbackVariant( + props.initialVariant, + props.highlightAt === 'init', + props.code || {}, + props.initial, + ).catch((error) => ({ error })); + if ('error' in loaded) { + return ; } - if (props.highlightAt === 'init' && typeof initialSource === 'string') { - const parseSource = props.parseSource; - if (!parseSource) { - return ( - - ); - } - - try { - initialSource = await parseSource(initialSource); - code[props.initialVariant] = { ...(code[props.initialVariant] || {}), source: initialSource }; - } catch (error) { - return ( - - ); - } - } - - // TODO: handle fallbackUsesExtraFiles and fallbackUsesAllVariants + const { code, initialFilename, initialSource, initialExtraFiles } = loaded; const propsWithInitialSource: CodeHighlighterWithInitialSourceProps = { ...props, code, initialFilename, initialSource, - initialExtraFiles: initial.extraFiles || {}, + initialExtraFiles, }; delete (propsWithInitialSource as any).initial; @@ -476,6 +190,7 @@ async function CodeInitialSourceLoader(props: CodeInitialSourceLoaderProps) { function CodeHighlighter(props: CodeHighlighterProps) { const ErrorHandler = props.ErrorHandler || HighlightErrorHandler; + if (props.precompute === true) { return ; } @@ -501,11 +216,7 @@ function CodeHighlighter(props: CodeHighlighterProps) { ...props, code, }; - delete (innerProps as any).ContentLoading; delete (innerProps as any).precompute; - delete (innerProps as any).initialVariant; - delete (innerProps as any).fallbackUsesExtraFiles; - delete (innerProps as any).fallbackUsesAllVariants; return ; } @@ -540,14 +251,15 @@ function CodeHighlighter(props: CodeHighlighterProps) { ); } - return ( - - ); + const propsWithInitial: CodeInitialSourceLoaderProps = { + ...props, + ContentLoading, + initialVariant: initialKey, + initial, + }; + delete (propsWithInitial as any).precompute; + + return ; } const propsWithInitialSource: CodeHighlighterWithInitialSourceProps = { @@ -559,6 +271,7 @@ function CodeHighlighter(props: CodeHighlighterProps) { initialSource, initialExtraFiles: initial.extraFiles, }; + delete (propsWithInitialSource as any).precompute; return ; } diff --git a/packages/docs-infra/src/CodeHighlighter/hast.tsx b/packages/docs-infra/src/CodeHighlighter/hast.tsx new file mode 100644 index 000000000..b01873079 --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/hast.tsx @@ -0,0 +1,50 @@ +import type { Nodes as HastNodes } from 'hast'; + +import { Fragment, jsx, jsxs } from 'react/jsx-runtime'; +import { toText } from 'hast-util-to-text'; +import { toJsxRuntime } from 'hast-util-to-jsx-runtime'; + +export function hastToJsx(hast: HastNodes): React.ReactNode { + return toJsxRuntime(hast, { Fragment, jsx, jsxs }); +} + +export function hastOrJsonToJsx(hastOrJson: HastNodes | { hastJson: string }): React.ReactNode { + let hast: HastNodes; + if ('hastJson' in hastOrJson) { + try { + hast = JSON.parse(hastOrJson.hastJson); + } catch (error) { + throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); + } + } else { + hast = hastOrJson; + } + + return toJsxRuntime(hast, { Fragment, jsx, jsxs }); +} + +export function stringOrHastToJsx( + source: string | HastNodes | { hastJson: string }, + highlighted?: boolean, +): React.ReactNode { + if (typeof source === 'string') { + return
{source}
; + } + + let hast: HastNodes; + if ('hastJson' in source) { + try { + hast = JSON.parse(source.hastJson); + } catch (error) { + throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`); + } + } else { + hast = source; + } + + if (highlighted) { + return
{hastToJsx(hast)}
; + } + + return
{toText(hast)}
; +} diff --git a/packages/docs-infra/src/CodeHighlighter/index.test.tsx b/packages/docs-infra/src/CodeHighlighter/index.test.tsx index fe930f67a..16334fc35 100644 --- a/packages/docs-infra/src/CodeHighlighter/index.test.tsx +++ b/packages/docs-infra/src/CodeHighlighter/index.test.tsx @@ -1,5 +1,6 @@ -import CodeHighlighter, { ContentProps } from './CodeHighlighter'; -import { hastOrJsonToJsx } from './CodeHighlighter'; +import type { ContentProps } from './types'; +import CodeHighlighter from './CodeHighlighter'; +import { hastOrJsonToJsx } from './hast'; function Content(props: ContentProps) { return
{hastOrJsonToJsx(props.code.Default.source)}
; diff --git a/packages/docs-infra/src/CodeHighlighter/loadFallbackVariant.ts b/packages/docs-infra/src/CodeHighlighter/loadFallbackVariant.ts new file mode 100644 index 000000000..3188e923d --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/loadFallbackVariant.ts @@ -0,0 +1,93 @@ +import type { + Code, + VariantCode, + VariantExtraFiles, + ParseSource, + LoadSource, + LoadVariantCode, + VariantSource, +} from './types'; + +export type FallbackVariants = { + code: Code; + initialFilename: string; + initialSource: VariantSource; + initialExtraFiles?: VariantExtraFiles; +}; + +export async function loadFallbackVariant( + initialVariant: string, + shouldHighlight?: boolean, + loaded?: Code, + initial?: VariantCode, + url?: string, + parseSource?: ParseSource, + loadSource?: LoadSource, + loadVariantCode?: LoadVariantCode, +): Promise { + if (!loaded) { + loaded = {}; + } + + if (!initial) { + if (!loadVariantCode) { + throw new Error( + '"loadVariantCode" function is required when initial filenames are not provided', + ); + } + + try { + initial = await loadVariantCode(initialVariant, url); + loaded[initialVariant] = initial; + } catch (error) { + throw new Error( + `Failed to load initial variant code (variant: ${initialVariant}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + const initialFilename = initial.fileName; + let initialSource = initial.source; + if (!initialSource) { + if (!loadSource) { + throw new Error('"loadSource" function is required when initial source is not provided'); + } + + try { + initialSource = await loadSource(initialVariant, initialFilename, url); + loaded[initialVariant] = { ...(loaded[initialVariant] || {}), source: initialSource }; + } catch (error) { + throw new Error( + `Failed to load initial source code (variant: ${initialVariant}, file: ${initialFilename}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + if (typeof initialSource === 'string' && shouldHighlight) { + if (!parseSource) { + throw new Error( + '"parseSource" function is required when initial source is a string and highlightAt is "init"', + ); + } + + try { + initialSource = await parseSource(initialSource); + loaded[initialVariant] = { ...(loaded[initialVariant] || {}), source: initialSource }; + } catch (error) { + throw new Error( + `Failed to parse initial source code (variant: ${initialVariant}, file: ${initialFilename}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + initialSource; + + // TODO: handle fallbackUsesExtraFiles and fallbackUsesAllVariants + + return { + code: loaded, + initialFilename, + initialSource, + initialExtraFiles: initial.extraFiles || {}, + }; +} diff --git a/packages/docs-infra/src/CodeHighlighter/loadVariant.ts b/packages/docs-infra/src/CodeHighlighter/loadVariant.ts new file mode 100644 index 000000000..6d383d88e --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/loadVariant.ts @@ -0,0 +1,65 @@ +import type { VariantCode, ParseSource, LoadSource, LoadVariantCode } from './types'; + +export async function loadVariant( + variantName: any, + url?: string, + variant?: VariantCode, + parseSource?: ParseSource, + loadSource?: LoadSource, + loadVariantCode?: LoadVariantCode, +): Promise<{ variant: string; code: VariantCode }> { + if (!variant) { + if (!loadVariantCode) { + throw new Error('"loadVariantCode" function is required when filenames are not provided'); + } + + try { + variant = await loadVariantCode(variantName, url); + } catch (error) { + throw new Error( + `Failed to load variant code (variant: ${variantName}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + const filename = variant.fileName; + let source = variant.source; + if (!source) { + if (!loadSource) { + throw new Error('"loadSource" function is required when source is not provided'); + } + + try { + source = await loadSource(variantName, filename, url); + variant = { ...variant, source }; + } catch (error) { + throw new Error( + `Failed to load source code (variant: ${variantName}, file: ${filename}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + if (typeof source === 'string') { + if (!parseSource) { + throw new Error( + '"parseSource" function is required when source is a string and highlightAt is "init"', + ); + } + + try { + source = await parseSource(source); + variant = { ...variant, source }; + } catch (error) { + throw new Error( + `Failed to parse source code (variant: ${variantName}, file: ${filename}, url: ${url}): ${JSON.stringify(error)}`, + ); + } + } + + // TODO: extraFiles handling + + return { + variant: variantName, + code: variant, + }; +} diff --git a/packages/docs-infra/src/CodeHighlighter/types.ts b/packages/docs-infra/src/CodeHighlighter/types.ts new file mode 100644 index 000000000..a1f38258e --- /dev/null +++ b/packages/docs-infra/src/CodeHighlighter/types.ts @@ -0,0 +1,63 @@ +import type { Nodes as HastNodes } from 'hast'; + +export type Components = { [key: string]: React.ReactNode }; + +type CodeMeta = { + fileName: string; +}; + +export type VariantSource = string | HastNodes | { hastJson: string }; +export type VariantExtraFiles = { + [fileName: string]: null | (CodeMeta & { source?: VariantSource }); +}; +export type VariantCode = CodeMeta & { + source?: VariantSource; + extraFiles?: VariantExtraFiles; +}; +export type Code = { [key: string]: VariantCode }; + +type ParsedVariantCode = CodeMeta & { + source: HastNodes | { hastJson: string }; + extraFiles: { [fileName: string]: CodeMeta & { source: HastNodes | { hastJson: string } } }; +}; +type ParsedCode = { [key: string]: ParsedVariantCode }; + +type Options = { name?: string; slug?: string; description?: string }; +export type ContentProps = { code: ParsedCode; components?: Components } & Options; +export type ContentLoadingProps = { fileNames: string[]; source: React.ReactNode }; + +type ErrorHandler = React.ComponentType<{ error: Error }>; + +interface CodeHighlighterBaseProps extends Options { + Content: React.ComponentType; + code?: Code; + components?: Components; + variant?: string; + initialVariant?: string; + defaultVariant?: string; + precompute?: boolean | Code; + ErrorHandler?: ErrorHandler; + ContentLoading?: React.ComponentType; + fallbackUsesExtraFiles?: boolean; + fallbackUsesAllVariants?: boolean; + url?: string; +} + +export type Fallback = React.ReactNode; + +export interface CodeHighlighterClientProps extends CodeHighlighterBaseProps { + highlightAt?: 'init' | 'hydration' | 'idle'; + fallback?: Fallback; +} + +export type LoadVariantCode = (variantName: string, url?: string) => Promise; +export type LoadSource = (variantName: string, fileName: string, url?: string) => Promise; +export type ParseSource = (source: string) => Promise; + +export interface CodeHighlighterProps extends CodeHighlighterBaseProps { + highlightAt?: 'init' | 'stream' | 'hydration' | 'idle'; + clientOnly?: boolean; + loadVariantCode?: LoadVariantCode; + loadSource?: LoadSource; + parseSource?: ParseSource; +} From b76621b3a19215b491824fbb770dada87e34a6ba Mon Sep 17 00:00:00 2001 From: dav-is Date: Thu, 10 Jul 2025 21:53:45 -0400 Subject: [PATCH 004/210] Stabilize API --- packages/docs-infra/docs/README.md | 4 +- .../app/components/code-controller/page.mdx | 7 + .../_mocks/checkbox/index.tsx | 6 +- .../code-highlighter/demos/Code.tsx | 25 +- .../demos/code-controlled/CodeController.tsx | 20 ++ .../demos/code-controlled/ControlledCode.tsx | 22 ++ .../demos/code-controlled/EditableCode.tsx | 18 + .../demos/code-controlled/index.ts | 13 + .../code-highlighter/demos/createDemo.tsx | 12 +- .../demos/demo-fallback/index.ts | 0 .../demos/base/HighlightProvider.tsx | 10 + .../highlight-provider/demos/base/index.ts | 13 + .../demos/fetch/HighlightProvider.tsx | 39 +++ .../highlight-provider/demos/fetch/index.ts | 13 + .../components/highlight-provider/page.mdx | 5 + .../docs-infra/docs/app/components/page.mdx | 7 + .../docs-infra/docs/app/contributing/page.mdx | 3 + packages/docs-infra/docs/app/hooks/page.mdx | 5 + packages/docs-infra/docs/app/page.mdx | 41 +++ packages/docs-infra/docs/next.config.ts | 7 + packages/docs-infra/docs/package.json | 2 +- packages/docs-infra/package.json | 35 +- .../CodeControllerContext.tsx | 29 ++ .../src/CodeControllerContext/index.ts | 1 + .../src/CodeHighlighter/CodeHighlighter.tsx | 156 +++++---- .../CodeHighlighter/CodeHighlighterClient.tsx | 326 +++++++++++++++++- .../CodeHighlighterContext.tsx | 24 ++ .../CodeHighlighterFallbackContext.tsx | 21 ++ .../CodeHighlighter/codeToFallbackProps.ts | 71 ++++ .../docs-infra/src/CodeHighlighter/errors.ts | 1 + .../src/CodeHighlighter/hasAllVariants.ts | 38 ++ .../src/CodeHighlighter/index.test.tsx | 5 +- .../docs-infra/src/CodeHighlighter/index.ts | 3 +- .../CodeHighlighter/loadFallbackVariant.ts | 12 +- .../src/CodeHighlighter/loadVariant.ts | 2 +- .../src/CodeHighlighter/maybeInitialData.ts | 90 +++++ .../docs-infra/src/CodeHighlighter/types.ts | 43 ++- .../src/CodeProvider/CodeContext.tsx | 18 + .../src/CodeProvider/CodeProvider.tsx | 37 ++ packages/docs-infra/src/CodeProvider/index.ts | 1 + .../HighlightProvider/HighlightContext.tsx | 16 - .../HighlightProvider/HighlightProvider.tsx | 22 -- .../docs-infra/src/HighlightProvider/index.ts | 3 - .../src/{CodeHighlighter => hast}/hast.tsx | 6 +- packages/docs-infra/src/hast/index.ts | 1 + packages/docs-infra/src/useCode/index.ts | 1 + packages/docs-infra/src/useCode/useCode.ts | 3 + packages/docs-infra/src/useDemo/index.ts | 1 + packages/docs-infra/src/useOnHydrate/index.ts | 1 + .../src/useOnHydrate/useOnHydrate.ts | 15 + packages/docs-infra/src/useOnIdle/index.ts | 1 + .../docs-infra/src/useOnIdle/useOnIdle.ts | 37 ++ pnpm-lock.yaml | 247 ++++++++++++- 53 files changed, 1367 insertions(+), 172 deletions(-) create mode 100644 packages/docs-infra/docs/app/components/code-controller/page.mdx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code-controlled/CodeController.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code-controlled/ControlledCode.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code-controlled/EditableCode.tsx create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/code-controlled/index.ts create mode 100644 packages/docs-infra/docs/app/components/code-highlighter/demos/demo-fallback/index.ts create mode 100644 packages/docs-infra/docs/app/components/highlight-provider/demos/base/HighlightProvider.tsx create mode 100644 packages/docs-infra/docs/app/components/highlight-provider/demos/base/index.ts create mode 100644 packages/docs-infra/docs/app/components/highlight-provider/demos/fetch/HighlightProvider.tsx create mode 100644 packages/docs-infra/docs/app/components/highlight-provider/demos/fetch/index.ts create mode 100644 packages/docs-infra/docs/app/components/highlight-provider/page.mdx create mode 100644 packages/docs-infra/docs/app/components/page.mdx create mode 100644 packages/docs-infra/docs/app/contributing/page.mdx create mode 100644 packages/docs-infra/docs/app/hooks/page.mdx create mode 100644 packages/docs-infra/docs/app/page.mdx create mode 100644 packages/docs-infra/src/CodeControllerContext/CodeControllerContext.tsx create mode 100644 packages/docs-infra/src/CodeControllerContext/index.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/CodeHighlighterContext.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/CodeHighlighterFallbackContext.tsx create mode 100644 packages/docs-infra/src/CodeHighlighter/codeToFallbackProps.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/errors.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/hasAllVariants.ts create mode 100644 packages/docs-infra/src/CodeHighlighter/maybeInitialData.ts create mode 100644 packages/docs-infra/src/CodeProvider/CodeContext.tsx create mode 100644 packages/docs-infra/src/CodeProvider/CodeProvider.tsx create mode 100644 packages/docs-infra/src/CodeProvider/index.ts delete mode 100644 packages/docs-infra/src/HighlightProvider/HighlightContext.tsx delete mode 100644 packages/docs-infra/src/HighlightProvider/HighlightProvider.tsx delete mode 100644 packages/docs-infra/src/HighlightProvider/index.ts rename packages/docs-infra/src/{CodeHighlighter => hast}/hast.tsx (91%) create mode 100644 packages/docs-infra/src/hast/index.ts create mode 100644 packages/docs-infra/src/useCode/index.ts create mode 100644 packages/docs-infra/src/useCode/useCode.ts create mode 100644 packages/docs-infra/src/useOnHydrate/index.ts create mode 100644 packages/docs-infra/src/useOnHydrate/useOnHydrate.ts create mode 100644 packages/docs-infra/src/useOnIdle/index.ts create mode 100644 packages/docs-infra/src/useOnIdle/useOnIdle.ts diff --git a/packages/docs-infra/docs/README.md b/packages/docs-infra/docs/README.md index 4bb63f87f..6f6894314 100644 --- a/packages/docs-infra/docs/README.md +++ b/packages/docs-infra/docs/README.md @@ -1,3 +1,5 @@ # MUI Docs-Infra Docs -This package contains the documentation for the MUI Docs-Infra project, which is responsible for the infrastructure and tooling used in the MUI documentation site. +This package contains the documentation for the MUI Docs-Infra project, which is responsible for the infrastructure and tooling used in the various MUI documentation sites. + +[Read the docs](./app/page.mdx) diff --git a/packages/docs-infra/docs/app/components/code-controller/page.mdx b/packages/docs-infra/docs/app/components/code-controller/page.mdx new file mode 100644 index 000000000..5cd808f87 --- /dev/null +++ b/packages/docs-infra/docs/app/components/code-controller/page.mdx @@ -0,0 +1,7 @@ +# Code Controller + +Controls the code displayed in the [`CodeHighlighter`](../code-highlighter/page.mdx) component. It allows you to change the code dynamically, to update the code based on user input. + +import { CodeEditorDemo } from './demos/code-editor'; + + diff --git a/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx index 9a1163268..4267a0f32 100644 --- a/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx +++ b/packages/docs-infra/docs/app/components/code-highlighter/_mocks/checkbox/index.tsx @@ -1,7 +1,11 @@ import * as React from 'react'; import styles from './index.module.css'; -export function Checkbox({ defaultChecked }: { defaultChecked?: boolean }) { +type CheckboxProps = { + defaultChecked: boolean; +}; + +export function Checkbox({ defaultChecked }: CheckboxProps) { return (