Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs-infra] Type interface API pages #14138

Merged
merged 14 commits into from
Sep 11, 2024
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"@types/luxon": "^3.4.2",
"@types/moment-hijri": "^2.1.4",
"@types/moment-jalaali": "^0.7.9",
"@types/prop-types": "^15.7.12",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"@types/stylis": "^4.2.6",
Expand Down
38 changes: 36 additions & 2 deletions docs/scripts/api/buildInterfacesDocumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,40 @@ type BuildApiInterfacesJsonOptions = BuildInterfacesCommonOptions & {
interfacesWithDedicatedPage: DocumentedInterfaces;
};

export interface InterfaceApiContent {
/**
* The name of the documented interface.
*/
name: string;
/**
* The array of way to import this interface.
*/
imports: string[];
/**
* The HTML content of the demonstrations list.
*/
demos?: string;
/**
* The mapping of property name to their typing.
*/
properties: {
[property: string]: {
/**
* The initial type definition.
*/
type: { description: string };
default?: string;
required?: true;
isProPlan?: true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason not to have a plan?: 'pro' | 'premium' | 'community' here?

Copy link
Member Author

@alexfauquette alexfauquette Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No other reason than "that's what the script returns"

I tried to do the update, but that would require a PR on the core repo to update properties section component

https://github.com/mui/material-ui/blob/master/docs/src/modules/components/ApiPage/sections/PropertiesSection.tsx/#L66-L69

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably make sense to update it at some point, but it's out of the scope of this PR then 👍

isPremiumPlan?: true;
};
};
}

export interface InterfaceApiTranslation {
interfaceDescription: string;
propertiesDescriptions: { [property: string]: { description: string } };
}
export async function buildApiInterfacesJson(options: BuildApiInterfacesJsonOptions) {
const { projects, apiPagesFolder, folder, interfaces, interfacesWithDedicatedPage } = options;

Expand Down Expand Up @@ -295,14 +329,14 @@ export async function buildInterfacesDocumentationPage(

const slug = kebabCase(parsedInterface.name);

const content = {
const content: InterfaceApiContent = {
name: parsedInterface.name,
imports: generateImportStatement(parsedInterface, projects),
...extractDemos(parsedInterface.tags.demos),
properties: {},
};

const translations = {
const translations: InterfaceApiTranslation = {
interfaceDescription: renderMarkdown(
linkify(
escapeCell(parsedInterface.description || ''),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,37 @@ import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
import { alpha } from '@mui/material/styles';
import { useTranslate, useUserLanguage } from '@mui/docs/i18n';
import { Translate, useTranslate, useUserLanguage } from '@mui/docs/i18n';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import { MarkdownElement } from '@mui/docs/MarkdownElement';
import { SectionTitle } from '@mui/docs/SectionTitle';
import { SectionTitle, SectionTitleProps } from '@mui/docs/SectionTitle';
import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs';
import PropertiesSection from 'docs/src/modules/components/ApiPage/sections/PropertiesSection';
import { DEFAULT_API_LAYOUT_STORAGE_KEYS } from 'docs/src/modules/components/ApiPage/sections/ToggleDisplayOption';
import { PropertyDefinition } from 'docs/src/modules/components/ApiPage/definitions/properties';
import { LayoutStorageKeys } from 'docs/src/modules/components/ApiPage';
import {
DEFAULT_API_LAYOUT_STORAGE_KEYS,
ApiDisplayOptions,
} from 'docs/src/modules/components/ApiPage/sections/ToggleDisplayOption';
import {
InterfaceApiTranslation,
InterfaceApiContent,
} from 'docsx/scripts/api/buildInterfacesDocumentation';
import { TableOfContentsEntry } from '@mui/internal-markdown';
import kebabCase from 'lodash/kebabCase';

export function getTranslatedHeader(t, header) {
type HeaderHash = 'demos' | 'import';

export function getTranslatedHeader(t: Translate, header: HeaderHash) {
const translations = {
demos: t('api-docs.demos'),
import: t('api-docs.import'),
};

// TODO Drop runtime type-checking once we type-check this file
if (!translations.hasOwnProperty(header)) {
throw new TypeError(
`Unable to translate header '${header}'. Did you mean one of '${Object.keys(
translations,
).join("', '")}'`,
);
}

return translations[header] || header;
}

function Heading(props) {
function Heading(props: Pick<SectionTitleProps<HeaderHash>, 'hash' | 'level'>) {
const { hash, level = 'h2' } = props;
const t = useTranslate();

Expand All @@ -44,7 +48,62 @@ Heading.propTypes = {
level: PropTypes.string,
};

export default function ApiPage(props) {
interface ApiPageProps {
descriptions: {
[lang: string]: InterfaceApiTranslation & {
// Table of Content added by the mapApiPageTranslations function
componentDescriptionToc: TableOfContentsEntry[];
};
};
pageContent: InterfaceApiContent;
defaultLayout?: ApiDisplayOptions;
/**
* The localStorage key used to save the user layout for each section.
* It's useful to dave different preferences on different pages.
* For example, the data grid has a different key that the core.
*/
layoutStorageKey?: LayoutStorageKeys;
}

interface GetInterfaceApiDefinitionsParams {
interfaceName: string;
properties: InterfaceApiContent['properties'];
propertiesDescriptions: InterfaceApiTranslation['propertiesDescriptions'];
/**
* Add indicators that the properties is optional instead of showing it is required.
*/
showOptionalAbbr?: boolean;
}

export function getInterfaceApiDefinitions(
params: GetInterfaceApiDefinitionsParams,
): PropertyDefinition[] {
const { properties, propertiesDescriptions, interfaceName, showOptionalAbbr = false } = params;

return Object.entries(properties).map(([propertyName, propertyData]) => {
const isRequired = propertyData.required && !showOptionalAbbr;
const isOptional = !propertyData.required && showOptionalAbbr;

const typeName = propertyData.type.description;
const propDefault = propertyData.default;
const propDescription = propertiesDescriptions[propertyName];

return {
propName: propertyName,
hash: `${kebabCase(interfaceName)}-prop-${propertyName}`,
propertyName,
description: propDescription?.description,
isOptional,
isRequired,
typeName,
propDefault,
isProPlan: propertyData.isProPlan,
isPremiumPlan: propertyData.isPremiumPlan,
};
});
}

export default function ApiPage(props: ApiPageProps) {
const {
descriptions,
pageContent,
Expand All @@ -54,21 +113,17 @@ export default function ApiPage(props) {
const t = useTranslate();
const userLanguage = useUserLanguage();

const { demos, filename = '', properties } = pageContent;
const { demos, properties } = pageContent;

const { componentDescription, propertiesDescriptions, interfaceDescription } =
descriptions[userLanguage];
const description = t('api-docs.pageDescription').replace(/{{name}}/, pageContent.name);

// Prefer linking the .tsx or .d.ts for the "Edit this page" link.
const apiSourceLocation = filename.replace('.js', '.d.ts');
const { propertiesDescriptions, interfaceDescription } = descriptions[userLanguage];
const description = t('api-docs.interfacePageDescription').replace(/{{name}}/, pageContent.name);

return (
<AppLayoutDocs
description={description}
disableToc={false}
toc={[]}
location={apiSourceLocation}
location=""
title={`${pageContent.name} API`}
disableAd
>
Expand All @@ -79,7 +134,7 @@ export default function ApiPage(props) {
component="p"
className="description"
gutterBottom
dangerouslySetInnerHTML={{ __html: interfaceDescription }}
dangerouslySetInnerHTML={{ __html: description }}
/>
<Heading hash="demos" />
{demos && (
Expand Down Expand Up @@ -131,26 +186,29 @@ export default function ApiPage(props) {
language="jsx"
/>

{componentDescription ? (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering what componentDescription was used for - is it okay to discard completely?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, component descript does not exist for interfaces. It has interfaceDescription which is already used a bit above in

<Typography
  variant="h5"
  component="p"
  className="description"
  gutterBottom
  dangerouslySetInnerHTML={{ __html: interfaceDescription }}
/>

It's visible for example in GridActionsColDef API

image

I replace the usual API page description

API reference docs for the React {{componentName}} component. Learn about the props, CSS, and other APIs of this exported module.

But that could probably be improved. I will check with other X memebers what they want for this page

{interfaceDescription ? (
<React.Fragment>
<br />
<br />
<span
dangerouslySetInnerHTML={{
__html: componentDescription,
__html: interfaceDescription,
}}
/>
</React.Fragment>
) : null}

<PropertiesSection
properties={properties}
propertiesDescriptions={propertiesDescriptions}
componentName={pageContent.name}
properties={getInterfaceApiDefinitions({
propertiesDescriptions,
properties,
interfaceName: pageContent.name,
showOptionalAbbr: true,
})}
title="api-docs.properties"
titleHash="properties"
defaultLayout={defaultLayout}
layoutStorageKey={layoutStorageKey.props}
showOptionalAbbr
/>
</MarkdownElement>
<svg style={{ display: 'none' }} xmlns="http://www.w3.org/2000/svg">
Expand All @@ -162,15 +220,13 @@ export default function ApiPage(props) {
);
}

ApiPage.propTypes = {
defaultLayout: PropTypes.oneOf(['collapsed', 'expanded', 'table']),
descriptions: PropTypes.object.isRequired,
layoutStorageKey: PropTypes.shape({
props: PropTypes.string,
}),
pageContent: PropTypes.object.isRequired,
};

if (process.env.NODE_ENV !== 'production') {
ApiPage.propTypes = exactProp(ApiPage.propTypes);
ApiPage.propTypes = exactProp({
defaultLayout: PropTypes.oneOf(['collapsed', 'expanded', 'table']),
descriptions: PropTypes.object.isRequired,
layoutStorageKey: PropTypes.shape({
props: PropTypes.string,
}),
pageContent: PropTypes.object.isRequired,
});
}
3 changes: 3 additions & 0 deletions docs/translations/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,8 @@
"/components/backdrop": "Backdrop",
"/components/alert": "Alert",
"/components/pagination": "Pagination"
},
"api-docs": {
"interfacePageDescription": "API reference docs for the {{name}} interface. Learn about the properties other APIs of this exported module."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have the default be something like:

Suggested change
"interfacePageDescription": "API reference docs for the {{name}} interface. Learn about the properties other APIs of this exported module."
"interfacePageDescription": "Extended documentation for the {{name}} interface with detailed information on the module's properties and available APIs."

}
}
Loading