From d9c3bf4dc949bdb10a2acf7da93751039df4af44 Mon Sep 17 00:00:00 2001 From: alexandre Date: Thu, 25 May 2023 18:33:29 +0200 Subject: [PATCH] [WIP] Implement the new API page --- docs/src/modules/components/ApiPage.js | 2 + .../src/modules/components/ApiPage/ApiPage.js | 499 ++++++++++++++++++ .../modules/components/ApiPage/ApiTitle.tsx | 98 ++++ .../modules/components/ApiPage/CSSList.tsx | 116 ++++ .../src/modules/components/PropertiesTable.js | 194 +++---- 5 files changed, 801 insertions(+), 108 deletions(-) create mode 100644 docs/src/modules/components/ApiPage/ApiPage.js create mode 100644 docs/src/modules/components/ApiPage/ApiTitle.tsx create mode 100644 docs/src/modules/components/ApiPage/CSSList.tsx diff --git a/docs/src/modules/components/ApiPage.js b/docs/src/modules/components/ApiPage.js index 448ddc0b4bbdfb..57c93337073010 100644 --- a/docs/src/modules/components/ApiPage.js +++ b/docs/src/modules/components/ApiPage.js @@ -11,6 +11,7 @@ import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; import Ad from 'docs/src/modules/components/Ad'; import { sxChip } from './AppNavDrawerItem'; +import CSSList from './ApiPage/CSSList'; function CSSTable(props) { const { componentStyles, classDescriptions } = props; @@ -427,6 +428,7 @@ import { ${pageContent.name} } from '${source}';`}

+

+ + + {t('api-docs.ruleName')} + {t('api-docs.globalClass')} + {t('api-docs.description')} + + + + {componentStyles.classes.map((className) => { + const isGlobalStateClass = !!componentStyles.globalClasses[className]; + return ( + + + + {isGlobalStateClass ? ( + + {className} + + + ) : ( + className + )} + + + + + . + {componentStyles.globalClasses[className] || + `${componentStyles.name}-${className}`} + + + + + ); + })} + + + ); +} + +CSSTable.propTypes = { + classDescriptions: PropTypes.object.isRequired, + componentStyles: PropTypes.object.isRequired, +}; + +export function SlotsTable(props) { + const { componentSlots, slotDescriptions } = props; + const t = useTranslate(); + + return ( + + + + + + + + + + + {componentSlots.map(({ class: className, name, default: defaultValue }) => { + return ( + + + + + + ); + })} + +
{t('api-docs.name')}{t('api-docs.defaultClass')}{t('api-docs.defaultHTMLTag')}{t('api-docs.description')}
+ {name} + + + + {defaultValue && {defaultValue}} + +
+ ); +} + +SlotsTable.propTypes = { + componentSlots: PropTypes.array.isRequired, + slotDescriptions: PropTypes.object.isRequired, +}; + +export function ClassesTable(props) { + const { componentClasses, classDescriptions, componentName } = props; + const t = useTranslate(); + + const list = componentClasses.classes.map((classes) => ({ + classes, + className: + componentClasses.globalClasses[classes] || + `Mui${componentName.replace('Unstyled', '')}-${classes}`, + })); + + return ( + + + + + + + + + {list + .sort((a, b) => a.className.localeCompare(b.className)) + .map((item) => { + const isGlobalStateClass = !!componentClasses.globalClasses[item.classes]; + return ( + + + + ); + })} + +
{t('api-docs.globalClass')}{t('api-docs.description')}
+ + . + {isGlobalStateClass ? ( + + {item.className} + + + ) : ( + item.className + )} + + +
+ ); +} + +ClassesTable.propTypes = { + classDescriptions: PropTypes.object.isRequired, + componentClasses: PropTypes.object.isRequired, + componentName: PropTypes.string.isRequired, +}; + +export function getTranslatedHeader(t, header) { + const translations = { + demos: t('api-docs.demos'), + import: t('api-docs.import'), + props: t('api-docs.props'), + 'theme-default-props': t('api-docs.themeDefaultProps'), + inheritance: t('api-docs.inheritance'), + slots: t('api-docs.slots'), + classes: t('api-docs.classes'), + css: t('api-docs.css'), + }; + + // 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) { + const { hash, level: Level = 'h2' } = props; + const t = useTranslate(); + + return ( + + {getTranslatedHeader(t, hash)} + + + + + + + ); +} + +Heading.propTypes = { + hash: PropTypes.string.isRequired, + level: PropTypes.string, +}; + +export default function ApiPage(props) { + const { descriptions, disableAd = false, pageContent } = props; + const t = useTranslate(); + const userLanguage = useUserLanguage(); + + const { + cssComponent, + demos, + filename, + forwardsRefTo, + inheritance, + props: componentProps, + spread, + styles: componentStyles, + slots: componentSlots, + classes: componentClasses, + } = pageContent; + + const isJoyComponent = filename.includes('mui-joy'); + const isBaseComponent = filename.includes('mui-base'); + const defaultPropsLink = isJoyComponent + ? '/joy-ui/customization/themed-components/#theme-default-props' + : '/material-ui/customization/theme-components/#theme-default-props'; + const styleOverridesLink = isJoyComponent + ? '/joy-ui/customization/themed-components/#theme-style-overrides' + : '/material-ui/customization/theme-components/#theme-style-overrides'; + let slotGuideLink = ''; + if (isJoyComponent) { + slotGuideLink = '/joy-ui/customization/themed-components/#component-identifier'; + } else if (isBaseComponent) { + slotGuideLink = '/base/getting-started/customization/#overriding-subcomponent-slots'; + } + + const { + componentDescription, + componentDescriptionToc = [], + classDescriptions, + propDescriptions, + slotDescriptions, + } = descriptions[userLanguage]; + const description = t('api-docs.pageDescription').replace(/{{name}}/, pageContent.name); + + const source = filename + .replace(/\/packages\/mui(-(.+?))?\/src/, (match, dash, pkg) => `@mui/${pkg}`) + // convert things like `/Table/Table.js` to `` + .replace(/\/([^/]+)\/\1\.(js|tsx)$/, ''); + + // Prefer linking the .tsx or .d.ts for the "Edit this page" link. + const apiSourceLocation = filename.replace('.js', '.d.ts'); + + const hasClasses = + componentClasses?.classes?.length || + Object.keys(componentClasses?.classes?.globalClasses || {}).length; + + function createTocEntry(sectionName) { + return { + text: getTranslatedHeader(t, sectionName), + hash: sectionName, + children: [ + ...(sectionName === 'props' && inheritance + ? [{ text: t('api-docs.inheritance'), hash: 'inheritance', children: [] }] + : []), + ...(sectionName === 'props' && pageContent.themeDefaultProps + ? [{ text: t('api-docs.themeDefaultProps'), hash: 'theme-default-props', children: [] }] + : []), + ], + }; + } + + const toc = [ + createTocEntry('demos'), + createTocEntry('import'), + ...componentDescriptionToc, + createTocEntry('props'), + componentStyles.classes.length > 0 && createTocEntry('css'), + componentSlots?.length > 0 && createTocEntry('slots'), + hasClasses && createTocEntry('classes'), + ].filter(Boolean); + + // The `ref` is forwarded to the root element. + let refHint = t('api-docs.refRootElement'); + if (forwardsRefTo == null) { + // The component cannot hold a ref. + refHint = t('api-docs.refNotHeld'); + } + + let spreadHint = ''; + if (spread) { + // Any other props supplied will be provided to the root element ({{spreadHintElement}}). + spreadHint = t('api-docs.spreadHint').replace( + /{{spreadHintElement}}/, + inheritance + ? `${inheritance.component}` + : t('api-docs.nativeElement'), + ); + } + + let inheritanceSuffix = ''; + if (inheritance && inheritance.component === 'Transition') { + inheritanceSuffix = t('api-docs.inheritanceSuffixTransition'); + } + + return ( + + +

{pageContent.name} API

+ + {description} + {disableAd ? null : } + + +
For examples and details on the usage of this React component, visit the component demo pages:

+ ${demos}`, + }} + /> + + + + {componentDescription ? ( + +
+
+ +
+ ) : null} + +

+ +
+ {cssComponent && ( + + +
+
+
+ )} + + {inheritance && ( + + + + + )} + {pageContent.themeDefaultProps && ( + + + + + )} + {Object.keys(componentStyles.classes).length ? ( + + +

+ +
+

+ + + ) : null} + {componentSlots?.length ? ( + + + {slotGuideLink && ( +

+ )} + +
+

+ + + ) : null} + {hasClasses ? ( + + +

+ +
+ + ) : null} + + + + + + + + ); +} + +ApiPage.propTypes = { + descriptions: PropTypes.object.isRequired, + disableAd: PropTypes.bool, + pageContent: PropTypes.object.isRequired, +}; + +if (process.env.NODE_ENV !== 'production') { + ApiPage.propTypes = exactProp(ApiPage.propTypes); +} diff --git a/docs/src/modules/components/ApiPage/ApiTitle.tsx b/docs/src/modules/components/ApiPage/ApiTitle.tsx new file mode 100644 index 00000000000000..8ba41d37df30d6 --- /dev/null +++ b/docs/src/modules/components/ApiPage/ApiTitle.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react/no-danger */ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { alpha, styled } from '@mui/material/styles'; +import { green, grey, lightBlue } from '@mui/material/colors'; +import { + brandingDarkTheme as darkTheme, + brandingLightTheme as lightTheme, +} from 'docs/src/modules/brandingTheme'; + +const Root = styled('p')( + ({ theme }) => ({ + display: 'flex', + '& span': { + borderBottom: 'solid 1px', + borderColor: `var(--muidocs-palette-grey-200, ${darkTheme.palette.grey[200]})`, + padding: '2px 6px', + }, + '&:hover span': { + borderColor: `var(--muidocs-palette-blue-700, ${lightTheme.palette.primary[700]})`, + }, + '& .MuiApi-item-title': { + borderWidth: '1px', + borderStyle: 'solid', + borderTopLeftRadius: '4px', + borderTopRightRadius: '4px', + borderBottomLeftRadius: '4px', + color: `var(--muidocs-palette-primary-700, ${lightTheme.palette.primary[700]})`, + fontWeight: theme.typography.fontWeightSemiBold, + backgroundColor: grey[200], + }, + '&:hover .MuiApi-item-title': { + backgroundColor: alpha(lightBlue[100], 0.5), + }, + '& .MuiApi-item-description': { + flexGrow: 1, + color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`, + }, + '& .MuiApi-item-note': { + padding: '2px 6px', + color: `var(--muidocs-palette-green-800, ${green[800]})`, + }, + }), + ({ theme }) => ({ + [`:where(${theme.vars ? '[data-mui-color-scheme="dark"]' : '.mode-dark'}) &`]: { + color: 'rgb(255, 255, 255)', + '& span': { + borderColor: '#2F3A46', + }, + '&:hover span': { + borderColor: `var(--muidocs-palette-primary-400, ${lightTheme.palette.primary[400]})`, + }, + '& .MuiApi-item-title': { + color: `var(--muidocs-palette-primary-200, ${lightTheme.palette.primary[200]})`, + backgroundColor: '#1F262E', + }, + '&:hover .MuiApi-item-title': { + backgroundColor: `var(--muidocs-palette-primary-light-200, #0059B24D) / 30%`, + }, + '& .MuiApi-item-description': { + color: '#B2BAC2', + }, + '& .MuiApi-item-note': { + color: `var(--muidocs-palette-green-400, ${green[400]})`, + }, + }, + }), +); + +export type ApiTitleProps = { + title: string; + description?: string; + note?: string; +}; + +function ApiTitle(props: ApiTitleProps) { + const { title, description, note } = props; + return ( + + {title} + /g, ' '), + }} + /> + {note && {note}} + + ); +} + +ApiTitle.propTypes = { + description: PropTypes.string, + note: PropTypes.string, + title: PropTypes.string.isRequired, +}; + +export default ApiTitle; diff --git a/docs/src/modules/components/ApiPage/CSSList.tsx b/docs/src/modules/components/ApiPage/CSSList.tsx new file mode 100644 index 00000000000000..214665969303ad --- /dev/null +++ b/docs/src/modules/components/ApiPage/CSSList.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react/no-danger */ +import * as React from 'react'; +import PropTypes from 'prop-types'; + +import { useTranslate } from 'docs/src/modules/utils/i18n'; +import ApiTitle from './ApiTitle'; + +export type CSSListProps = { + componentStyles: { + classes: string[]; + globalClasses: { [classeKey: string]: string }; + name: null | string; + }; + classDescriptions: { + [classeKey: string]: { + description: string; + nodeName?: string; + conditions?: string; + }; + }; +}; + +export default function CSSList(props: CSSListProps) { + const { componentStyles, classDescriptions } = props; + const t = useTranslate(); + + return ( + + {/*

  • + Prop this is a class + Required +
  • + + + + + + + + + */} + {componentStyles.classes.map((className) => { + const isGlobalStateClass = !!componentStyles.globalClasses[className]; + return ( + + +

    + + //

    + // + // + // + ); + })} + {/* +
    {t('api-docs.ruleName')}{t('api-docs.globalClass')}{t('api-docs.description')}
    + // + // {isGlobalStateClass ? ( + // + // {className} + // + // + // ) : ( + // className + // )} + // + // + // + // . + // {componentStyles.globalClasses[className] || + // `${componentStyles.name}-${className}`} + // + // + //
    */} + + ); +} + +CSSList.propTypes = { + classDescriptions: PropTypes.object.isRequired, + componentStyles: PropTypes.object.isRequired, +}; diff --git a/docs/src/modules/components/PropertiesTable.js b/docs/src/modules/components/PropertiesTable.js index f36a97aa1e4a1c..e7e419dc24a01a 100644 --- a/docs/src/modules/components/PropertiesTable.js +++ b/docs/src/modules/components/PropertiesTable.js @@ -1,125 +1,103 @@ /* eslint-disable react/no-danger */ import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import Alert from '@mui/material/Alert'; -import { alpha, styled } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; import { useTranslate } from 'docs/src/modules/utils/i18n'; - -const Asterisk = styled('abbr')(({ theme }) => ({ color: theme.palette.error.main })); +import ApiTitle from './ApiPage/ApiTitle'; const Wrapper = styled('div')({ overflow: 'hidden', }); -const Table = styled('table')(({ theme }) => { - const contentColor = 'rgba(255, 255, 255, 1)'; - const contentColorDark = alpha(theme.palette.primaryDark[900], 1); - const contentColorTransparent = 'rgba(255, 255, 255, 0)'; - const contentColorTransparentDark = alpha(theme.palette.primaryDark[900], 0); - const shadowColor = 'rgba(0,0,0,0.2)'; - const shadowColorDark = 'rgba(0,0,0,0.7)'; - return { - borderRadius: 10, - background: ` - linear-gradient(to right, ${contentColor} 5%, ${contentColorTransparent}), - linear-gradient(to right, ${contentColorTransparent}, ${contentColor} 100%) 100%, - linear-gradient(to right, ${shadowColor}, rgba(0, 0, 0, 0) 5%), - linear-gradient(to left, ${shadowColor}, rgba(0, 0, 0, 0) 5%)`, - backgroundAttachment: 'local, local, scroll, scroll', - // the above background create thin line on the left and right sides of the table - // as a workaround, use negative margin with overflow `hidden` on the parent - marginLeft: -1, - marginRight: -1, - ...theme.applyDarkStyles({ - background: ` - linear-gradient(to right, ${contentColorDark} 5%, ${contentColorTransparentDark}), - linear-gradient(to right, ${contentColorTransparentDark}, ${contentColorDark} 100%) 100%, - linear-gradient(to right, ${shadowColorDark}, rgba(0, 0, 0, 0) 5%), - linear-gradient(to left, ${shadowColorDark}, rgba(0, 0, 0, 0) 5%)`, - }), - }; -}); export default function PropertiesTable(props) { const { properties, propertiesDescriptions, showOptionalAbbr = false } = props; - const t = useTranslate(); - - const showDefaultPropColumn = Object.entries(properties).some( - ([, propData]) => propData.default != null, - ); + // const t = useTranslate(); return ( - - - - - - {showDefaultPropColumn && } - - - - - {Object.entries(properties).map(([propName, propData]) => { - const typeName = propData.type.description || propData.type.name; - const propDefault = propData.default; - return ( - propData.description !== '@ignore' && ( - - - - {showDefaultPropColumn && ( - - )} - - - ) - ); - })} - -
    {t('api-docs.name')}{t('api-docs.type')}{t('api-docs.default')}{t('api-docs.description')}
    - - {propName} - {propData.required && !showOptionalAbbr && ( - - * - - )} - {!propData.required && showOptionalAbbr && ( - - ? - - )} - - - - - {propDefault && {propDefault}} - - {propData.deprecated && ( - - {t('api-docs.deprecated')} - {propData.deprecationInfo && ' - '} - {propData.deprecationInfo && ( - - )} - - )} -
    -
    + {Object.entries(properties) + .filter(([, propData]) => propData.description !== '@ignore') + .map(([propName, propData]) => { + // ApiTitle + const typeName = propData.type.description || propData.type.name; + const propDefault = propData.default; + return ( + + +
    + {propDefault && ( +

    + Default value: {propDefault} +

    + )} + + ); + // + // + // + // {propName} + // {propData.required && !showOptionalAbbr && ( + // + // * + // + // )} + // {!propData.required && showOptionalAbbr && ( + // + // ? + // + // )} + // + // + // + // + // + // {showDefaultPropColumn && ( + // + // {propDefault && {propDefault}} + // + // )} + // + // {propData.deprecated && ( + // + // {t('api-docs.deprecated')} + // {propData.deprecationInfo && ' - '} + // {propData.deprecationInfo && ( + // + // )} + // + // )} + //
    + // + // + // ); + })} + {/* + */} ); }