diff --git a/docs/package.json b/docs/package.json index 6b1e887e152ea..ecbbf4d0ccd0b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -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", diff --git a/docs/scripts/api/buildInterfacesDocumentation.ts b/docs/scripts/api/buildInterfacesDocumentation.ts index 91b9a814a355c..4dbd278ff2f36 100644 --- a/docs/scripts/api/buildInterfacesDocumentation.ts +++ b/docs/scripts/api/buildInterfacesDocumentation.ts @@ -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; + 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; @@ -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 || ''), diff --git a/docs/src/modules/components/InterfaceApiPage.js b/docs/src/modules/components/InterfaceApiPage.tsx similarity index 58% rename from docs/src/modules/components/InterfaceApiPage.js rename to docs/src/modules/components/InterfaceApiPage.tsx index e0a0402091532..c63c509cbba9e 100644 --- a/docs/src/modules/components/InterfaceApiPage.js +++ b/docs/src/modules/components/InterfaceApiPage.tsx @@ -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, 'hash' | 'level'>) { const { hash, level = 'h2' } = props; const t = useTranslate(); @@ -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, @@ -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 ( @@ -79,7 +134,7 @@ export default function ApiPage(props) { component="p" className="description" gutterBottom - dangerouslySetInnerHTML={{ __html: interfaceDescription }} + dangerouslySetInnerHTML={{ __html: description }} /> {demos && ( @@ -131,26 +186,29 @@ export default function ApiPage(props) { language="jsx" /> - {componentDescription ? ( + {interfaceDescription ? (

) : null} + @@ -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, + }); } diff --git a/docs/translations/translations.json b/docs/translations/translations.json index 2677973858def..58c53a610540c 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -142,5 +142,8 @@ "/components/backdrop": "Backdrop", "/components/alert": "Alert", "/components/pagination": "Pagination" + }, + "api-docs": { + "interfacePageDescription": "Extended documentation for the {{name}} interface with detailed information on the module's properties and available APIs." } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d00160344186f..c5a7dc6e4d58e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -678,6 +678,9 @@ importers: '@types/moment-jalaali': specifier: ^0.7.9 version: 0.7.9 + '@types/prop-types': + specifier: ^15.7.12 + version: 15.7.12 '@types/react-dom': specifier: ^18.3.0 version: 18.3.0