Skip to content

Commit

Permalink
[docs-infra] Simplify API sections typing (#43128)
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandre Fauquette <[email protected]>
  • Loading branch information
alexfauquette authored Aug 8, 2024
1 parent 75c24b0 commit b9a041d
Show file tree
Hide file tree
Showing 19 changed files with 566 additions and 320 deletions.
90 changes: 58 additions & 32 deletions docs/src/modules/components/ApiPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { ComponentApiContent, PropsTranslations } from '@mui-internal/api-docs-builder';
import exactProp from '@mui/utils/exactProp';
import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import { TableOfContentsEntry } from '@mui/internal-markdown';
import { Ad, AdGuest } from '@mui/docs/Ad';
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
import WarningRoundedIcon from '@mui/icons-material/WarningRounded';
Expand All @@ -13,18 +15,29 @@ import { BrandingProvider } from '@mui/docs/branding';
import { SectionTitle, SectionTitleProps } from '@mui/docs/SectionTitle';
import { MarkdownElement } from '@mui/docs/MarkdownElement';
import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs';
import PropertiesSection, {
getPropsToC,
} from 'docs/src/modules/components/ApiPage/sections/PropertiesSection';
import ClassesSection, {
getClassesToC,
} from 'docs/src/modules/components/ApiPage/sections/ClassesSection';
import PropertiesSection from 'docs/src/modules/components/ApiPage/sections/PropertiesSection';
import ClassesSection from 'docs/src/modules/components/ApiPage/sections/ClassesSection';
import SlotsSection from 'docs/src/modules/components/ApiPage/sections/SlotsSection';
import {
ApiDisplayOptions,
DEFAULT_API_LAYOUT_STORAGE_KEYS,
} from 'docs/src/modules/components/ApiPage/sections/ToggleDisplayOption';
import { TableOfContentsEntry } from '@mui/internal-markdown';
import {
getPropertiesToC,
getPropsApiDefinitions,
} from 'docs/src/modules/components/ApiPage/definitions/properties';
import {
getClassApiDefinitions,
getClassesToC,
} from 'docs/src/modules/components/ApiPage/definitions/classes';
import { getSlotsApiDefinitions } from './ApiPage/definitions/slots';

// TODO Move this type definition to the AppLayoutDocs file when moved to TS
export interface TableOfContentsParams {
children: (TableOfContentsParams | TableOfContentsEntry)[];
hash: string;
text: string;
}

type ApiHeaderKeys =
| 'demos'
Expand Down Expand Up @@ -150,6 +163,23 @@ export default function ApiPage(props: ApiPageProps) {
// Prefer linking the .tsx or .d.ts for the "Edit this page" link.
const apiSourceLocation = filename.replace('.js', '.d.ts');

// Merge data and translation
const propertiesDef = getPropsApiDefinitions({
componentName: pageContent.name,
properties: componentProps,
propertiesDescriptions: propDescriptions,
});
const classesDef = getClassApiDefinitions({
componentClasses,
componentName: pageContent.name,
classDescriptions,
});
const slotsDef = getSlotsApiDefinitions({
componentSlots,
componentName: pageContent.name,
slotDescriptions,
});

function createTocEntry(sectionName: ApiHeaderKeys) {
return {
text: getTranslatedHeader(t, sectionName),
Expand All @@ -165,23 +195,13 @@ export default function ApiPage(props: ApiPageProps) {
};
}

const toc = [
const toc: TableOfContentsParams[] = [
createTocEntry('demos'),
createTocEntry('import'),
...componentDescriptionToc,
getPropsToC({
t,
componentName: pageContent.name,
componentProps,
inheritance,
themeDefaultProps: pageContent.themeDefaultProps,
}),
componentSlots?.length > 0 && createTocEntry('slots'),
...getClassesToC({
t,
componentName: pageContent.name,
componentClasses,
}),
getPropertiesToC({ properties: propertiesDef, hash: 'props', t }),
...(componentSlots?.length > 0 ? [createTocEntry('slots')] : []),
...getClassesToC({ classes: classesDef, t }),
].filter(Boolean);

// The `ref` is forwarded to the root element.
Expand Down Expand Up @@ -272,9 +292,7 @@ export default function ApiPage(props: ApiPageProps) {
</React.Fragment>
) : null}
<PropertiesSection
properties={componentProps}
propertiesDescriptions={propDescriptions}
componentName={pageContent.name}
properties={propertiesDef}
spreadHint={spreadHint}
defaultLayout={defaultLayout}
layoutStorageKey={layoutStorageKey.props}
Expand Down Expand Up @@ -325,23 +343,17 @@ export default function ApiPage(props: ApiPageProps) {
/>
</React.Fragment>
)}
(
<SlotsSection
componentSlots={componentSlots}
slotDescriptions={slotDescriptions}
componentName={pageContent.name}
slots={slotsDef}
spreadHint={
slotGuideLink &&
t('api-docs.slotDescription').replace(/{{slotGuideLink}}/, slotGuideLink)
}
defaultLayout={defaultLayout}
layoutStorageKey={layoutStorageKey.slots}
/>
)
<ClassesSection
componentClasses={componentClasses}
componentName={pageContent.name}
classDescriptions={classDescriptions}
classes={classesDef}
spreadHint={t('api-docs.classesDescription')}
styleOverridesLink={styleOverridesLink}
defaultLayout={defaultLayout}
Expand All @@ -357,3 +369,17 @@ export default function ApiPage(props: ApiPageProps) {
</AppLayoutDocs>
);
}

if (process.env.NODE_ENV !== 'production') {
ApiPage.propTypes = exactProp({
defaultLayout: PropTypes.oneOf(['collapsed', 'expanded', 'table']),
descriptions: PropTypes.object.isRequired,
disableAd: PropTypes.bool,
layoutStorageKey: PropTypes.shape({
classes: PropTypes.string,
props: PropTypes.string,
slots: PropTypes.string,
}),
pageContent: PropTypes.object.isRequired,
});
}
82 changes: 82 additions & 0 deletions docs/src/modules/components/ApiPage/definitions/classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { PropsTranslations, ComponentApiContent } from '@mui-internal/api-docs-builder';
import { Translate } from '@mui/docs/i18n';
import kebabCase from 'lodash/kebabCase';
import type { TableOfContentsParams } from 'docs/src/modules/components/ApiPage';

export interface ClassDefinition {
className: string;
key: string;
hash: string;
description?: string;
isGlobal?: boolean;
isDeprecated?: boolean;
deprecationInfo?: string;
}

export type GetCssToCParams = {
classes: ClassDefinition[];
t: Translate;
hash?: string;
};

export const getClassesToC = ({ classes, t, hash }: GetCssToCParams): TableOfContentsParams[] =>
!classes || classes.length === 0
? []
: [
{
text: t('api-docs.classes'),
hash: hash ?? 'classes',
children: [
...classes.map(({ key, hash: classeHash }) => ({
text: key,
hash: classeHash,
children: [],
})),
],
},
];

export interface GetClassApiDefinitionsParams {
componentClasses: ComponentApiContent['classes'];
classDescriptions: PropsTranslations['classDescriptions'];
componentName: string;
}

const errorMessage = (componentName: string, className: string, slotName: string): string =>
`${className} description from component ${componentName} should include ${slotName} since its definition includes "{{${slotName}}}"`;

export function getClassApiDefinitions(params: GetClassApiDefinitionsParams): ClassDefinition[] {
const { componentClasses, classDescriptions, componentName } = params;

return componentClasses.map((classDefinition) => {
const {
conditions,
nodeName,
deprecationInfo,
description: translatedDescription,
} = classDescriptions[classDefinition.key] ?? {}; // Not all classes have a description.

let description = translatedDescription ?? classDefinition.description;

if (description.includes('{{conditions}}')) {
if (!conditions) {
throw Error(errorMessage(componentName, classDefinition.className, 'conditions'));
}
description = description.replace(/{{conditions}}/, conditions);
}

if (description.includes('{{nodeName}}')) {
if (!nodeName) {
throw Error(errorMessage(componentName, classDefinition.className, 'nodeName'));
}
description = description.replace(/{{nodeName}}/, nodeName);
}

return {
...classDefinition,
description,
deprecationInfo,
hash: `${kebabCase(componentName)}-classes-${classDefinition.className}`,
};
});
}
Loading

0 comments on commit b9a041d

Please sign in to comment.