Warning
This is an internal project, and is not intended for public use. No support or stability guarantees are provided.
First, we have to create a TypesContent component. Create a new file at docs/components/TypesContent.tsx:
'use client';
import * as React from 'react';
import { useTypes } from '@mui/internal-docs-infra/useTypes';
import type {
ProcessedComponentTypeMeta,
ProcessedHookTypeMeta,
ProcessedFunctionTypeMeta,
ProcessedClassTypeMeta,
ProcessedMethod,
ProcessedRawTypeMeta,
ProcessedRawEnumMember,
ProcessedTypesMeta,
} from '@mui/internal-docs-infra/useTypes';
import { TypesContentProps } from '@mui/internal-docs-infra/abstractCreateTypes';
import { Table } from '@/components/Table';
import styles from './TypesTable.module.css';
export type TypesTableProps = TypesContentProps<{}>;
export function TypesTable(props: TypesTableProps) {
// Get the main type and additional types for this export
const { type, additionalTypes } = useTypes(props);
return (
<div className={styles.typesTable}>
{type && <TypeMetaDoc typeMeta={type} />}
{additionalTypes.map((typeMeta: ProcessedTypesMeta) => (
<details key={typeMeta.name} className={styles.additionalType}>
<summary className={styles.additionalTypeSummary}>{typeMeta.name}</summary>
<div id={typeMeta.slug}>
<TypeMetaDoc typeMeta={typeMeta} showName={false} />
</div>
</details>
))}
</div>
);
}
function TypeMetaDoc(props: { typeMeta: ProcessedTypesMeta; showName?: boolean }) {
const { typeMeta, showName = true } = props;
if (typeMeta.type === 'component') {
return <ComponentDoc type={typeMeta.data} showName={showName} />;
}
if (typeMeta.type === 'hook') {
return <HookDoc type={typeMeta.data} showName={showName} />;
}
if (typeMeta.type === 'function') {
return <FunctionDoc type={typeMeta.data} showName={showName} />;
}
if (typeMeta.type === 'class') {
return <ClassDoc type={typeMeta.data} showName={showName} />;
}
if (typeMeta.type === 'raw') {
return <RawDoc name={typeMeta.name} data={typeMeta.data} showName={showName} />;
}
return null;
}
function ComponentDoc(props: { type: ProcessedComponentTypeMeta; showName?: boolean }) {
const { type, showName = true } = props;
return (
<div className={styles.componentDoc}>
{showName && <div className={styles.componentName}>{type.name}</div>}
<div className={styles.componentDescription}>{type.description}</div>
{Object.keys(type.props).length > 0 && (
<Table>
<thead>
<tr>
<th>Prop</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(type.props).map((key) => {
const prop = type.props[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{prop.type}</td>
<td>{prop.description}</td>
</tr>
);
})}
</tbody>
</Table>
)}
{Object.keys(type.dataAttributes).length > 0 && (
<Table>
<thead>
<tr>
<th>Data Attribute</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
{Object.keys(type.dataAttributes).map((key) => {
const dataAttr = type.dataAttributes[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{dataAttr.description}</td>
<td>
{dataAttr.default !== undefined && (
<code>{JSON.stringify(dataAttr.default)}</code>
)}
</td>
</tr>
);
})}
</tbody>
</Table>
)}
{Object.keys(type.cssVariables).length > 0 && (
<Table>
<thead><tr>
<th>CSS Variable</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
{Object.keys(type.cssVariables).map((key) => {
const cssVar = type.cssVariables[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{cssVar.description}</td>
<td>
{cssVar.default !== undefined && <code>{JSON.stringify(cssVar.default)}</code>}
</td>
</tr>
);
})}
</tbody>
</Table>
)}
</div>
);
}
function HookDoc(props: { type: ProcessedHookTypeMeta; showName?: boolean }) {
const { type, showName = true } = props;
const { name, description, parameters, properties, returnValue } = type;
const paramsOrProps = properties ?? parameters ?? {};
const isProperties = Boolean(properties);
return (
<div className={styles.componentDoc}>
{showName && <div className={styles.componentName}>{name}</div>}
{description && <div className={styles.componentDescription}>{description}</div>}
{Object.keys(paramsOrProps).length > 0 && (
<Table>
<thead>
<tr>
<th>{isProperties ? 'Property' : 'Parameter'}</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(paramsOrProps).map((key) => {
const param = paramsOrProps[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{param.type}</td>
<td>{param.description}</td>
</tr>
);
})}
</tbody>
</Table>
)}
<div className={styles.returnType}>Return Type</div>
{(() => {
if (!returnValue) {
return null;
}
// Use discriminated union for type-safe checks
if (returnValue.kind === 'simple') {
return (
<div>
<div>{returnValue.type}</div>
{returnValue.description && <div>{returnValue.description}</div>}
</div>
);
}
// returnValue.kind === 'object'
return (
<React.Fragment>
{returnValue.typeName && (
<div>
<code>{returnValue.typeName}</code>
</div>
)}
<Table>
<thead>
<tr>
<th>Key</th>
<th>Type</th>
<th>Required</th>
</tr>
</thead>
<tbody>
{Object.keys(returnValue.properties).map((key) => {
const prop = returnValue.properties[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{prop.type}</td>
<td>{prop.required ? 'Yes' : 'No'}</td>
</tr>
);
})}
</tbody>
</Table>
</React.Fragment>
);
})()}
</div>
);
}
function FunctionDoc(props: { type: ProcessedFunctionTypeMeta; showName?: boolean }) {
const { type, showName = true } = props;
const { name, description, parameters, properties, returnValue } = type;
const paramsOrProps = properties ?? parameters ?? {};
const isProperties = Boolean(properties);
return (<div className={styles.componentDoc}>
{showName && <div className={styles.componentName}>{name}</div>}
{description && <div className={styles.componentDescription}>{description}</div>}
{Object.keys(paramsOrProps).length > 0 && (
<Table>
<thead>
<tr>
<th>{isProperties ? 'Property' : 'Parameter'}</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(paramsOrProps).map((key) => {
const param = paramsOrProps[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{param.type}</td>
<td>{param.description}</td>
</tr>
);
})}
</tbody>
</Table>
)}
<div className={styles.returnType}>Return Type</div>
{(() => {
if (!returnValue) {
return null;
}
// Use discriminated union for type-safe checks
if (returnValue.kind === 'simple') {
return (
<div>
<div>{returnValue.type}</div>
{returnValue.description && <div>{returnValue.description}</div>}
</div>
);
}
// returnValue.kind === 'object'
return (
<React.Fragment>
{returnValue.typeName && (
<div>
<code>{returnValue.typeName}</code>
</div>
)}
<Table>
<thead>
<tr>
<th>Key</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(returnValue.properties).map((key) => {
const prop = returnValue.properties[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{prop.type}</td>
<td>{prop.description}</td>
</tr>
);
})}
</tbody>
</Table>
</React.Fragment>
);
})()}
</div>
);
}
function ClassDoc(props: { type: ProcessedClassTypeMeta; showName?: boolean }) {
const { type, showName = true } = props;
const { name, description, constructorParameters, properties, methods } = type;
return (
<div className={styles.componentDoc}>
{showName && <div className={styles.componentName}>{name}</div>}
{description && <div className={styles.componentDescription}>{description}</div>}
{/* Static Methods first - often factory methods */}
{Object.keys(methods).length > 0 &&
(() => {
const methodEntries = Object.entries(methods) as [string, ProcessedMethod][];
const staticMethods = methodEntries.filter(([, m]) => m.isStatic);
return renderMethodsSection('Static Methods', staticMethods);
})()}
{Object.keys(constructorParameters).length > 0 && (
<React.Fragment>
<div className={styles.returnType}>Constructor Parameters</div>
<Table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(constructorParameters).map((key) => {
const param = constructorParameters[key];
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{param.type}</td>
<td>{param.default}</td>
<td>{param.description}</td>
</tr>
);
})}
</tbody>
</Table></React.Fragment>
)}
{Object.keys(properties).length > 0 && (
<React.Fragment>
<div className={styles.returnType}>Properties</div>
<Table>
<thead>
<tr>
<th>Property</th>
<th>Type</th>
<th>Modifiers</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(properties).map((key) => {
const prop = properties[key];
const modifiers: string[] = [];
if (prop.isStatic) {
modifiers.push('static');
}
if (prop.readonly) {
modifiers.push('readonly');
}
return (
<tr key={key}>
<td data-nowrap>{key}</td>
<td>{prop.type}</td>
<td>{modifiers.join(', ') || '-'}</td>
<td>{prop.description}</td>
</tr>
);
})}
</tbody>
</Table>
</React.Fragment>
)}
{/* Instance Methods */}
{Object.keys(methods).length > 0 &&
(() => {
const methodEntries = Object.entries(methods) as [string, ProcessedMethod][];
const instanceMethods = methodEntries.filter(([, m]) => !m.isStatic);
return renderMethodsSection('Methods', instanceMethods);
})()}
</div>
);
function renderMethodsSection(title: string, methodEntries: [string, ProcessedMethod][]) {
if (methodEntries.length === 0) {
return null;
}
return (
<React.Fragment>
<div className={styles.returnType}>{title}</div>
{methodEntries.map(([key, method]) => (
<div key={key} className={styles.methodDoc}>
<div className={styles.methodName}>{key}</div>
{method.description && (
<div className={styles.methodDescription}>{method.description}</div>
)}
{Object.keys(method.parameters).length > 0 && (
<Table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.keys(method.parameters).map((paramKey) => {
const param = method.parameters[paramKey];
return (
<tr key={paramKey}>
<td data-nowrap>{paramKey}</td>
<td>{param.type}</td>
<td>{param.description}</td>
</tr>
);
})}
</tbody>
</Table>
)}
{method.returnValue && (
<div>
<strong>Returns:</strong> {method.returnValue}
{method.returnValueDescription && <span> — {method.returnValueDescription}</span>}
</div>
)}
</div>
))}
</React.Fragment>
);
}
}
function RawDoc(props: { name: string; data: ProcessedRawTypeMeta; showName?: boolean }) {
const { name, data, showName = true } = props;
return (
<div className={styles.componentDoc}>
{showName && <div className={styles.componentName}>{name}</div>}
{data.description && <div className={styles.componentDescription}>{data.description}</div>}
{data.formattedCode && <div className={styles.typeContent}>{data.formattedCode}</div>}
{data.enumMembers && data.enumMembers.length > 0 && (
<Table>
<thead>
<tr>
<th>Member</th>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{data.enumMembers.map((member: ProcessedRawEnumMember) => (
<tr key={member.name}>
<td data-nowrap>{member.name}</td>
<td>{member.value}</td>
<td>{member.description}</td>
</tr>))}
</tbody>
</Table>
)}
</div>
);
}and a TypeRef component:
'use client';
import * as React from 'react';
import { Popover } from '@base-ui/react/popover';
import { useType } from '@mui/internal-docs-infra/useType';
import { Popup } from '../Popup';
import { ReferenceTable } from '../ReferenceTable/ReferenceTable';
interface TypeRefProps {
/** The anchor href for the type documentation */
href: string;
/** The matched identifier name (e.g., "Trigger", "Accordion.Trigger") */
name: string;
/** Optional CSS class name(s) inherited from the syntax highlighting span */
className?: string;
/** The rendered text content */
children: React.ReactNode;
}
/**
* Renders a type reference as an interactive element.
* When clicked, displays a popover showing the type's documentation
* rendered via `ReferenceTable`.
*
* Falls back to a standard anchor link when no type data is available.
*/
export function TypeRef({ href, name, className, children }: TypeRefProps) {
const typeData = useType(name);
// Fall back to a standard anchor if no type data is available
if (!typeData) {
return (
<a href={href} className={className}>
{children}
</a>
);
}
return (
<Popover.Root>
<Popover.Trigger
className={`${className ?? ''} cursor-pointer border-0 bg-transparent p-0 font-[inherit] text-[inherit] underline decoration-dotted decoration-[var(--color-violet)]`.trim()}
>
{children}
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner sideOffset={8}>
<Popover.Popup render={<Popup className="max-w-[min(32rem,var(--available-width))]" />}>
<div className="flex items-center justify-between border-b border-gray-200 px-4 py-2">
<span className="text-sm font-medium text-gray-900">{typeData.meta.name}</span>
<div className="flex items-center gap-2">
{typeData.meta.type !== 'raw' && (
<Popover.Close
render={<a href={href} />}
nativeButton={false}
className="text-xs text-gray-600 underline decoration-gray-300 hover:text-gray-900 hover:decoration-gray-500"
>
Full docs
</Popover.Close>
)}
<Popover.Close
aria-label="Close"
className="flex size-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-900"
>
<svg width="8" height="8" viewBox="0 0 8 8" fill="none">
<path d="M1 1L7 7M7 1L1 7" stroke="currentColor" strokeWidth="1.5" />
</svg>
</Popover.Close>
</div>
</div>
<div className="p-4">
{typeData.meta.type === 'raw' ? (
typeData.meta.data.formattedCode
) : (
<ReferenceTable type={typeData.meta} additionalTypes={[]} hideDescription />
)}
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}Next, we have to add a createTypes() factory:
import 'server-only';
import {
createTypesFactory,
createMultipleTypesFactory,
} from '@mui/internal-docs-infra/abstractCreateTypes';
import { mdxComponents, mdxComponentsInline } from '../mdx-components';
import { TypesContent } from '../components/TypesContent';
const components = { pre: Pre, TypeRef };
const inlineComponents = {
pre: ({ children }: { children: React.ReactNode }) => <pre>{children}</pre>,
TypeRef,
};
const typeOptions = {
TypesContent,
components,
inlineComponents,
typeRefComponent: 'TypeRef',
};
/**
* Creates a type documentation component for a single component.
* @param url Depends on `import.meta.url` to determine the source file location.
* @param component The component to extract types from.
* @param [meta] Additional metadata for the types (injected by loader).
*/
export const createTypes = createTypesFactory({
TypesContent,
components,
inlineComponents,
typeRefComponent: 'TypeRef',
});
/**
* Creates type documentation components for multiple related components.
* Useful for component families like Checkbox.Root, Checkbox.Indicator.
* @param url Depends on `import.meta.url` to determine the source file location.
* @param components Object with multiple component exports.
* @param [meta] Additional metadata for the types (injected by loader).
*/
export const createMultipleTypes = createMultipleTypesFactory({
TypesContent,
components,
inlineComponents,
typeRefComponent: 'TypeRef',
});