From 0a29e3c5d07b3d8dcbc3a89d283f65c6c86f2c18 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 7 Nov 2022 16:18:10 +0100 Subject: [PATCH 001/314] [docs] Live demos v2 (#34870) --- .../material/components/app-bar/app-bar-pt.md | 8 +- .../material/components/app-bar/app-bar-zh.md | 8 +- .../material/components/app-bar/app-bar.md | 6 +- .../material/components/drawers/drawers-pt.md | 4 +- .../material/components/drawers/drawers-zh.md | 4 +- .../material/components/drawers/drawers.md | 4 +- docs/package.json | 2 + docs/packages/markdown/extractImports.js | 7 + docs/packages/markdown/loader.js | 25 +- .../src/modules/components/CodeCopyButton.tsx | 46 ++++ docs/src/modules/components/Demo.js | 245 +++++++++++++----- docs/src/modules/components/DemoEditor.tsx | 146 +++++++++++ .../modules/components/DemoEditorError.tsx | 35 +++ .../{DemoSandboxed.js => DemoSandbox.js} | 40 ++- .../src/modules/components/HighlightedCode.js | 45 +--- docs/src/modules/components/MarkdownDocs.js | 1 + docs/src/modules/components/ReactRunner.tsx | 50 ++++ docs/translations/translations.json | 1 + yarn.lock | 65 ++++- 19 files changed, 592 insertions(+), 150 deletions(-) create mode 100644 docs/packages/markdown/extractImports.js create mode 100644 docs/src/modules/components/CodeCopyButton.tsx create mode 100644 docs/src/modules/components/DemoEditor.tsx create mode 100644 docs/src/modules/components/DemoEditorError.tsx rename docs/src/modules/components/{DemoSandboxed.js => DemoSandbox.js} (84%) create mode 100644 docs/src/modules/components/ReactRunner.tsx diff --git a/docs/data/material/components/app-bar/app-bar-pt.md b/docs/data/material/components/app-bar/app-bar-pt.md index e5cd61dd7399f1..0881ad63c4b406 100644 --- a/docs/data/material/components/app-bar/app-bar-pt.md +++ b/docs/data/material/components/app-bar/app-bar-pt.md @@ -36,7 +36,7 @@ Uma barra de pesquisa lateral ## Responsive App bar with Drawer -{{"demo": "DrawerAppBar.js", "bg": true,"iframe": true}} +{{"demo": "DrawerAppBar.js", "bg": true, "iframe": true, "disableLiveEdit": true}} ## App bar with a primary search field @@ -103,19 +103,19 @@ You can use the `useScrollTrigger()` hook to respond to user scroll actions. The app bar hides on scroll down to leave more space for reading. -{{"demo": "HideAppBar.js", "iframe": true}} +{{"demo": "HideAppBar.js", "iframe": true, "disableLiveEdit": true}} ### Elevate App bar The app bar elevates on scroll to communicate that the user is not at the top of the page. -{{"demo": "ElevateAppBar.js", "iframe": true}} +{{"demo": "ElevateAppBar.js", "iframe": true, "disableLiveEdit": true}} ### Voltar ao topo A floating action buttons appears on scroll to make it easy to get back to the top of the page. -{{"demo": "BackToTop.js", "iframe": true}} +{{"demo": "BackToTop.js", "iframe": true, "disableLiveEdit": true}} ### `useScrollTrigger([options]) => trigger` diff --git a/docs/data/material/components/app-bar/app-bar-zh.md b/docs/data/material/components/app-bar/app-bar-zh.md index 596392551c7402..669a3918d92dc5 100644 --- a/docs/data/material/components/app-bar/app-bar-zh.md +++ b/docs/data/material/components/app-bar/app-bar-zh.md @@ -36,7 +36,7 @@ materialDesign: https://m2.material.io/components/app-bars-top ## 带有抽屉的响应式应用栏 -{{"demo": "DrawerAppBar.js", "bg": true,"iframe": true}} +{{"demo": "DrawerAppBar.js", "bg": true, "iframe": true, "disableLiveEdit": true}} ## 带有主搜索输入框的应用栏 @@ -103,19 +103,19 @@ function App() { 向下滚动隐藏应用栏,从而为阅读提供更多空间。 -{{"demo": "HideAppBar.js", "iframe": true}} +{{"demo": "HideAppBar.js", "iframe": true, "disableLiveEdit": true}} ### 提升应用栏 应用栏会在滚动时提升,以表明用户还未到页面的顶部。 -{{"demo": "ElevateAppBar.js", "iframe": true}} +{{"demo": "ElevateAppBar.js", "iframe": true, "disableLiveEdit": true}} ### 回到顶部 滚动时出现一个浮动操作按钮,以便返回页面的顶部。 -{{"demo": "BackToTop.js", "iframe": true}} +{{"demo": "BackToTop.js", "iframe": true, "disableLiveEdit": true}} ### `useScrollTrigger([options]) => trigger` diff --git a/docs/data/material/components/app-bar/app-bar.md b/docs/data/material/components/app-bar/app-bar.md index 01b98f6770c929..5df11c3b76e5b5 100644 --- a/docs/data/material/components/app-bar/app-bar.md +++ b/docs/data/material/components/app-bar/app-bar.md @@ -103,19 +103,19 @@ You can use the `useScrollTrigger()` hook to respond to user scroll actions. The app bar hides on scroll down to leave more space for reading. -{{"demo": "HideAppBar.js", "iframe": true}} +{{"demo": "HideAppBar.js", "iframe": true, "disableLiveEdit": true}} ### Elevate App bar The app bar elevates on scroll to communicate that the user is not at the top of the page. -{{"demo": "ElevateAppBar.js", "iframe": true}} +{{"demo": "ElevateAppBar.js", "iframe": true, "disableLiveEdit": true}} ### Back to top A floating action button appears on scroll to make it easy to get back to the top of the page. -{{"demo": "BackToTop.js", "iframe": true}} +{{"demo": "BackToTop.js", "iframe": true, "disableLiveEdit": true}} ### `useScrollTrigger([options]) => trigger` diff --git a/docs/data/material/components/drawers/drawers-pt.md b/docs/data/material/components/drawers/drawers-pt.md index 66583cf701fa4c..f48e8528380ad8 100644 --- a/docs/data/material/components/drawers/drawers-pt.md +++ b/docs/data/material/components/drawers/drawers-pt.md @@ -50,7 +50,7 @@ Você pode configurar a propriedade `SwipeableDrawer` para visualizar uma borda Se você estiver em uma área de trabalho, poderá alternar o drawer com o botão "OPEN". Se estiver pelo celular, abra a demonstração no CodeSandbox (ícone "editar") e deslizar. -{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "height": 400, "maxWidth": 300}} +{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "disableLiveEdit": true, "height": 400, "maxWidth": 300}} ### Navegação em altura total @@ -71,7 +71,7 @@ More details in the [Modal performance section](/material-ui/react-modal/#perfor You can use the `temporary` variant to display a drawer for small screens and `permanent` for a drawer for wider screens. -{{"demo": "ResponsiveDrawer.js", "iframe": true}} +{{"demo": "ResponsiveDrawer.js", "iframe": true, "disableLiveEdit": true}} ## Drawer persistente diff --git a/docs/data/material/components/drawers/drawers-zh.md b/docs/data/material/components/drawers/drawers-zh.md index a7a8108a28aa08..45713260999222 100644 --- a/docs/data/material/components/drawers/drawers-zh.md +++ b/docs/data/material/components/drawers/drawers-zh.md @@ -50,7 +50,7 @@ const iOS = 如果你使用的是桌面设备,那么可以点击 "OPEN" 按钮来切换抽屉的显示。 如果你使用的设备是手机,那么可以在 CodeSandbox(“编辑”图标)中打开该演示,并尝试滑动抽屉。 -{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "height": 400, "maxWidth": 300}} +{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "disableLiveEdit": true, "height": 400, "maxWidth": 300}} ### 全高导航栏 @@ -71,7 +71,7 @@ More details in the [Modal performance section](/material-ui/react-modal/#perfor You can use the `temporary` variant to display a drawer for small screens and `permanent` for a drawer for wider screens. -{{"demo": "ResponsiveDrawer.js", "iframe": true}} +{{"demo": "ResponsiveDrawer.js", "iframe": true, "disableLiveEdit": true}} ## 持久的抽屉 diff --git a/docs/data/material/components/drawers/drawers.md b/docs/data/material/components/drawers/drawers.md index 4c2f413ade3618..6186f63ce2519f 100644 --- a/docs/data/material/components/drawers/drawers.md +++ b/docs/data/material/components/drawers/drawers.md @@ -57,7 +57,7 @@ You can configure the `SwipeableDrawer` to have a visible edge when closed. If you are on a desktop, you can toggle the drawer with the "OPEN" button. If you are on mobile, you can open the demo in CodeSandbox ("edit" icon) and swipe. -{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "height": 400, "maxWidth": 300}} +{{"demo": "SwipeableEdgeDrawer.js", "iframe": true, "disableLiveEdit": true, "height": 400, "maxWidth": 300}} ### Keep mounted @@ -78,7 +78,7 @@ More details in the [Modal performance section](/material-ui/react-modal/#perfor You can use the `temporary` variant to display a drawer for small screens and `permanent` for a drawer for wider screens. -{{"demo": "ResponsiveDrawer.js", "iframe": true}} +{{"demo": "ResponsiveDrawer.js", "iframe": true, "disableLiveEdit": true}} ## Persistent drawer diff --git a/docs/package.json b/docs/package.json index 88ce9f45ef1cd5..003720224b39f0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -106,6 +106,8 @@ "react-is": "^18.2.0", "react-number-format": "^4.9.4", "react-router-dom": "^6.4.2", + "react-runner": "^1.0.3", + "react-simple-code-editor": "^0.13.1", "react-spring": "^8.0.27", "react-swipeable-views": "^0.14.0", "react-transition-group": "^4.4.5", diff --git a/docs/packages/markdown/extractImports.js b/docs/packages/markdown/extractImports.js new file mode 100644 index 00000000000000..3daefa092b7076 --- /dev/null +++ b/docs/packages/markdown/extractImports.js @@ -0,0 +1,7 @@ +const importModuleRegexp = /^import [^'"]* from ['"]([^'"\n ][^'"\n ]*)['"]/gm; + +function extractImports(code) { + return (code.match(importModuleRegexp) || []).map((x) => x.replace(importModuleRegexp, '$1')); +} + +module.exports = extractImports; diff --git a/docs/packages/markdown/loader.js b/docs/packages/markdown/loader.js index 18386c35d91fbb..b42605ce214b18 100644 --- a/docs/packages/markdown/loader.js +++ b/docs/packages/markdown/loader.js @@ -1,6 +1,7 @@ const { promises: fs, readdirSync } = require('fs'); const path = require('path'); const { prepareMarkdown } = require('./parseMarkdown'); +const extractImports = require('./extractImports'); const notEnglishMarkdownRegExp = /-([a-z]{2})\.md$/; // TODO: pass as argument @@ -123,6 +124,7 @@ module.exports = async function demoLoader() { const { docs } = prepareMarkdown({ pageFilename, translations, componentPackageMapping }); const demos = {}; + const importedModuleIDs = new Set(); const components = {}; const demoModuleIDs = new Set(); const componentModuleIDs = new Set(); @@ -158,6 +160,9 @@ module.exports = async function demoLoader() { raw: await fs.readFile(moduleFilepath, { encoding: 'utf8' }), }; demoModuleIDs.add(moduleID); + extractImports(demos[demoName].raw).forEach((importModuleID) => + importedModuleIDs.add(importModuleID), + ); try { const previewFilepath = moduleFilepath.replace(/\.js$/, '.tsx.preview'); @@ -212,6 +217,14 @@ module.exports = async function demoLoader() { }); const transformed = ` + ${Array.from(importedModuleIDs) + .map((moduleID) => { + return `import * as ${moduleIDToJSIdentifier( + moduleID.replace('@', '$'), + )} from '${moduleID}';`; + }) + .join('\n')} + ${Array.from(demoModuleIDs) .map((moduleID) => { return `import ${moduleIDToJSIdentifier(moduleID)} from '${moduleID}';`; @@ -224,6 +237,16 @@ module.exports = async function demoLoader() { .join('\n')} export const docs = ${JSON.stringify(docs, null, 2)}; export const demos = ${JSON.stringify(demos, null, 2)}; + +demos.scope = { + process: {}, + import: { +${Array.from(importedModuleIDs) + .map((moduleID) => ` "${moduleID}": ${moduleIDToJSIdentifier(moduleID.replace('@', '$'))},`) + .join('\n')} + }, +}; + export const demoComponents = { ${Array.from(demoModuleIDs) .map((moduleID) => { @@ -238,7 +261,7 @@ ${Array.from(componentModuleIDs) }) .join('\n')} }; - `; +`; return transformed; }; diff --git a/docs/src/modules/components/CodeCopyButton.tsx b/docs/src/modules/components/CodeCopyButton.tsx new file mode 100644 index 00000000000000..f64da582102db5 --- /dev/null +++ b/docs/src/modules/components/CodeCopyButton.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import copy from 'clipboard-copy'; + +interface CodeCopyButtonProps { + code: string; +} + +export default function CodeCopyButton(props: CodeCopyButtonProps) { + const { code, ...other } = props; + const [copied, setCopied] = React.useState(false); + // This component is designed to be wrapped in NoSsr + const macOS = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0; + const key = macOS ? '⌘' : 'Ctrl + '; + + React.useEffect(() => { + if (copied) { + const timeout = setTimeout(() => { + setCopied(false); + }, 2000); + return () => { + clearTimeout(timeout); + }; + } + return undefined; + }, [copied]); + + return ( + + ); +} diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js index bfc9854989bb91..2be1e42b13ad85 100644 --- a/docs/src/modules/components/Demo.js +++ b/docs/src/modules/components/Demo.js @@ -1,6 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useRouter } from 'next/router'; +import { debounce } from '@mui/material/utils'; import { alpha, styled } from '@mui/material/styles'; import { styled as joyStyled } from '@mui/joy/styles'; import { unstable_useId as useId } from '@mui/utils'; @@ -8,13 +9,26 @@ import IconButton from '@mui/material/IconButton'; import Collapse from '@mui/material/Collapse'; import NoSsr from '@mui/material/NoSsr'; import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; -import DemoSandboxed from 'docs/src/modules/components/DemoSandboxed'; +import DemoSandbox from 'docs/src/modules/components/DemoSandbox'; +import ReactRunner from 'docs/src/modules/components/ReactRunner'; +import DemoEditor from 'docs/src/modules/components/DemoEditor'; +import DemoEditorError from 'docs/src/modules/components/DemoEditorError'; import { AdCarbonInline } from 'docs/src/modules/components/AdCarbon'; +import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; import { useCodeVariant } from 'docs/src/modules/utils/codeVariant'; import { CODE_VARIANTS } from 'docs/src/modules/constants'; import { useUserLanguage, useTranslate } from 'docs/src/modules/utils/i18n'; import BrandingProvider from 'docs/src/BrandingProvider'; +/** + * Removes leading spaces (indentation) present in the `.tsx` previews + * to be able to replace the existing code with the incoming dynamic code + * @param {string} input + */ +function trimLeadingSpaces(input = '') { + return input.replace(/^\s+/gm, ''); +} + const DemoToolbar = React.lazy(() => import('./DemoToolbar')); // Sync with styles from DemoToolbar // Importing the styles results in no bundle size reduction @@ -40,45 +54,91 @@ function getDemoName(location) { function useDemoData(codeVariant, demo, githubLocation) { const userLanguage = useUserLanguage(); const router = useRouter(); - const asPathWithoutLang = router.asPath.replace(/^\/[a-zA-Z]{2}\//, '/'); - let product; - let name = 'Material UI'; - if (asPathWithoutLang.startsWith('/joy-ui/')) { - product = 'joy-ui'; - name = 'Joy UI'; - } - if (asPathWithoutLang.startsWith('/base/')) { - product = 'base'; - name = 'MUI Base'; - } - if (asPathWithoutLang.startsWith('/x/')) { - name = 'MUI X'; - } + const { canonicalAs } = pathnameToLanguage(router.asPath); + + return React.useMemo(() => { + let product; + let name = 'Material UI'; + if (canonicalAs.startsWith('/joy-ui/')) { + product = 'joy-ui'; + name = 'Joy UI'; + } else if (canonicalAs.startsWith('/base/')) { + product = 'base'; + name = 'MUI Base'; + } else if (canonicalAs.startsWith('/x/')) { + name = 'MUI X'; + } - const title = `${getDemoName(githubLocation)} demo — ${name}`; - if (codeVariant === CODE_VARIANTS.TS && demo.rawTS) { return { - codeVariant: CODE_VARIANTS.TS, - githubLocation: githubLocation.replace(/\.js$/, '.tsx'), - language: userLanguage, - raw: demo.rawTS, - Component: demo.tsx, - sourceLanguage: 'tsx', - title, + scope: demo.scope, + jsxPreview: demo.jsxPreview, + ...(codeVariant === CODE_VARIANTS.TS && demo.rawTS + ? { + codeVariant: CODE_VARIANTS.TS, + githubLocation: githubLocation.replace(/\.js$/, '.tsx'), + raw: demo.rawTS, + Component: demo.tsx, + sourceLanguage: 'tsx', + } + : { + codeVariant: CODE_VARIANTS.JS, + githubLocation, + raw: demo.raw, + Component: demo.js, + sourceLanguage: 'jsx', + }), + title: `${getDemoName(githubLocation)} demo — ${name}`, product, + language: userLanguage, }; - } + }, [canonicalAs, codeVariant, demo, githubLocation, userLanguage]); +} - return { - codeVariant: CODE_VARIANTS.JS, - githubLocation, - language: userLanguage, - raw: demo.raw, - Component: demo.js, - sourceLanguage: 'jsx', - title, - product, - }; +function useDemoElement({ + demoOptions, + demoData, + editorCode, + initialEditorCode, + setDebouncedError, +}) { + const debouncedSetError = React.useMemo( + () => debounce(setDebouncedError, 300), + [setDebouncedError], + ); + + React.useEffect(() => { + return () => { + debouncedSetError.clear(); + }; + }, [debouncedSetError]); + + // Memoize to avoid rendering the demo more than it needs to be. + // For example, avoid a render when the demo is hovered. + return React.useMemo(() => { + if ( + // No need for a live environment if the code matches with the component rendered server-side. + editorCode.value === initialEditorCode || + // A limitation from https://github.com/nihgwu/react-runner, we can inject the `window` of the iframe + demoOptions.disableLiveEdit + ) { + return ; + } + + return ( + + ); + }, [demoData, demoOptions.disableLiveEdit, editorCode, initialEditorCode, debouncedSetError]); } const Root = styled('div')(({ theme }) => ({ @@ -161,7 +221,7 @@ const DemoRootMaterial = styled('div', { )} 0px, transparent 50%), radial-gradient(at 80% 0%, #FFFFFF 0px, transparent 20%), radial-gradient(at 0% 95%, ${alpha(theme.palette.primary[100], 0.3)}, transparent 40%), - radial-gradient(at 0% 20%, ${theme.palette.primary[50]} 0px, transparent 50%), + radial-gradient(at 0% 20%, ${theme.palette.primary[50]} 0px, transparent 50%), radial-gradient(at 93% 85%, ${alpha(theme.palette.primary[100], 0.2)} 0px, transparent 50%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23003A75' fill-opacity='0.03'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");`, }), @@ -211,18 +271,12 @@ const DemoRootJoy = joyStyled('div', { }), })); -const Code = styled(HighlightedCode)(({ theme }) => ({ - padding: 0, - marginBottom: theme.spacing(1), - marginTop: theme.spacing(2), - [theme.breakpoints.up('sm')]: { - marginTop: theme.spacing(0), - }, +const DemoCodeViewer = styled(HighlightedCode)({ '& pre': { margin: '0 auto', maxHeight: 'min(68vh, 1000px)', }, -})); +}); const AnchorLink = styled('div')({ marginTop: -64, // height of toolbar @@ -239,8 +293,6 @@ const InitialFocus = styled(IconButton)(({ theme }) => ({ })); export default function Demo(props) { - const router = useRouter(); - const asPathWithoutLang = router.asPath.replace(/^\/[a-zA-Z]{2}\//, '/'); const { demo, demoOptions, disableAd, githubLocation, mode } = props; if (process.env.NODE_ENV !== 'production') { @@ -263,7 +315,6 @@ export default function Demo(props) { setDemoHovered(event.type === 'mouseenter'); }; - const DemoComponent = demoData.Component; const demoName = getDemoName(demoData.githubLocation); const demoSandboxedStyle = React.useMemo( () => ({ @@ -295,9 +346,11 @@ export default function Demo(props) { }, [demoName]); const showPreview = - !demoOptions.hideToolbar && demoOptions.defaultCodeOpen !== false && Boolean(demo.jsxPreview); + !demoOptions.hideToolbar && + demoOptions.defaultCodeOpen !== false && + Boolean(demoData.jsxPreview); - const [demoKey, resetDemo] = React.useReducer((key) => key + 1, 0); + const [demoKey, setDemoKey] = React.useReducer((key) => key + 1, 0); const demoId = `demo-${useId()}`; const demoSourceId = `demoSource-${useId()}`; @@ -308,9 +361,45 @@ export default function Demo(props) { const [showAd, setShowAd] = React.useState(false); const adVisibility = showAd && !disableAd && !demoOptions.disableAd; - const isJoy = asPathWithoutLang.startsWith('/joy-ui'); - const DemoRoot = asPathWithoutLang.startsWith('/joy-ui') ? DemoRootJoy : DemoRootMaterial; - const Wrapper = asPathWithoutLang.startsWith('/joy-ui') ? BrandingProvider : React.Fragment; + const DemoRoot = demoData.product === 'joy-ui' ? DemoRootJoy : DemoRootMaterial; + const Wrapper = demoData.product === 'joy-ui' ? BrandingProvider : React.Fragment; + + const isPreview = !codeOpen && showPreview; + const initialEditorCode = isPreview + ? demoData.jsxPreview + : // Prettier remove all the leading lines except for the last one, remove it as we don't + // need it in the live edit view. + demoData.raw.replace(/\n$/, ''); + + const [editorCode, setEditorCode] = React.useState({ + value: initialEditorCode, + isPreview, + }); + + const resetDemo = () => { + setEditorCode({ + value: initialEditorCode, + isPreview, + }); + setDemoKey(); + }; + + React.useEffect(() => { + setEditorCode({ + value: initialEditorCode, + isPreview, + }); + }, [initialEditorCode, isPreview]); + + const [debouncedError, setDebouncedError] = React.useState(null); + + const demoElement = useDemoElement({ + demoOptions, + demoData, + editorCode, + initialEditorCode, + setDebouncedError, + }); return ( @@ -322,25 +411,26 @@ export default function Demo(props) { onMouseEnter={handleDemoHover} onMouseLeave={handleDemoHover} > - + - + > + {demoElement} + - + {demoOptions.hideToolbar ? null : ( }> }> @@ -367,16 +457,37 @@ export default function Demo(props) { )} - + {demoOptions.disableLiveEdit ? ( + + ) : ( + { + setEditorCode({ + ...editorCode, + value, + }); + }} + id={demoSourceId} + language={demoData.sourceLanguage} + copyButtonProps={{ + 'data-ga-event-category': codeOpen ? 'demo-expand' : 'demo', + 'data-ga-event-label': demoOptions.demo, + 'data-ga-event-action': 'copy-click', + }} + > + {debouncedError} + + )} {adVisibility ? : null} diff --git a/docs/src/modules/components/DemoEditor.tsx b/docs/src/modules/components/DemoEditor.tsx new file mode 100644 index 00000000000000..49f51845f16660 --- /dev/null +++ b/docs/src/modules/components/DemoEditor.tsx @@ -0,0 +1,146 @@ +import * as React from 'react'; +import SimpleCodeEditor from 'react-simple-code-editor'; +import Box from '@mui/material/Box'; +import NoSsr from '@mui/base/NoSsr'; +import { styled } from '@mui/material/styles'; +import prism from '@mui/markdown/prism'; +import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; +import CodeCopyButton from 'docs/src/modules/components/CodeCopyButton'; +import { useTranslate } from 'docs/src/modules/utils/i18n'; +import { useCodeCopy } from 'docs/src/modules/utils/CodeCopy'; +import { blue, blueDark } from 'docs/src/modules/brandingTheme'; + +const StyledMarkdownElement = styled(MarkdownElement)(({ theme }) => ({ + '& .scrollContainer': { + maxHeight: 'min(68vh, 1000px)', + overflow: 'auto', + backgroundColor: `${blueDark[800]} !important`, + borderRadius: theme.shape.borderRadius, + colorScheme: 'dark', + '&:hover': { + boxShadow: `0 0 0 3px ${ + theme.palette.mode === 'dark' ? theme.palette.primaryDark[400] : theme.palette.primary.light + }`, + }, + '&:focus-within': { + boxShadow: `0 0 0 2px ${ + theme.palette.mode === 'dark' ? theme.palette.primaryDark.main : theme.palette.primary.main + }`, + }, + }, +})) as any; + +const StyledSimpleCodeEditor = styled(SimpleCodeEditor)(({ theme }) => ({ + ...theme.typography.body2, + fontSize: theme.typography.pxToRem(13), + fontFamily: theme.typography.fontFamilyCode, + fontWeight: 400, + WebkitFontSmoothing: 'subpixel-antialiased', + color: '#f8f8f2', + direction: 'ltr /*! @noflip */' as any, + float: 'left', + minWidth: '100%', + '& pre': { + // The scroll container needs to be the parent of the editor + maxHeight: 'initial', + maxWidth: 'initial', + }, + '& textarea': { + outline: 'none', + }, + '& > textarea, & > pre': { + whiteSpace: 'pre !important', + }, +})); + +interface DemoEditorProps { + children: React.ReactNode; + copyButtonProps: {}; + id: string; + language: string; + onChange: () => {}; + value: string; +} + +export default function DemoEditor(props: DemoEditorProps) { + const { language, value, onChange, copyButtonProps, children, id } = props; + const t = useTranslate(); + const wrapperRef = React.useRef(null); + const enterRef = React.useRef(null); + const handlers = useCodeCopy(); + + React.useEffect(() => { + wrapperRef.current!.querySelector('textarea')!.tabIndex = -1; + }, []); + + return ( + { + if (event.key === 'Tab') { + return; + } + + if (event.key === 'Escape') { + enterRef.current!.focus(); + return; + } + + if (event.key === 'Enter') { + const textarea = wrapperRef.current!.querySelector('textarea'); + if (textarea !== document.activeElement) { + event.preventDefault(); + event.stopPropagation(); + textarea!.focus(); + } + } + }} + > +
+
+ + `${prism(code, language)}` + } + id={id} + value={value} + onValueChange={onChange} + /> +
+ ({ + position: 'absolute', + top: theme.spacing(1), + padding: theme.spacing(0.5, 1), + outline: 'none', + left: '50%', + border: '1px solid', + borderColor: blue[400], + backgroundColor: blueDark[600], + color: blueDark[50], + transform: 'translateX(-50%)', + borderRadius: '4px', + fontSize: theme.typography.pxToRem(13), + transition: 'all 0.3s', + '&:not(:focus)': { + top: 0, + opacity: 0, + pointerEvents: 'none', + }, + })} + dangerouslySetInnerHTML={{ + __html: t('editorHint'), + }} + /> + + + + {children} +
+
+ ); +} diff --git a/docs/src/modules/components/DemoEditorError.tsx b/docs/src/modules/components/DemoEditorError.tsx new file mode 100644 index 00000000000000..6418b60b858855 --- /dev/null +++ b/docs/src/modules/components/DemoEditorError.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import Alert, { AlertProps } from '@mui/material/Alert'; + +export default function DemoEditorError(props: AlertProps) { + if (!props.children) { + return null; + } + + return ( + + ); +} diff --git a/docs/src/modules/components/DemoSandboxed.js b/docs/src/modules/components/DemoSandbox.js similarity index 84% rename from docs/src/modules/components/DemoSandboxed.js rename to docs/src/modules/components/DemoSandbox.js index b8a5a1ab657ec1..85648240ee2e9c 100644 --- a/docs/src/modules/components/DemoSandboxed.js +++ b/docs/src/modules/components/DemoSandbox.js @@ -14,6 +14,7 @@ import { useTheme, styled, createTheme, ThemeProvider } from '@mui/material/styl import rtl from 'jss-rtl'; import DemoErrorBoundary from 'docs/src/modules/components/DemoErrorBoundary'; import { useTranslate } from 'docs/src/modules/utils/i18n'; +import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; import { getDesignTokens } from 'docs/src/modules/brandingTheme'; import { highDensity } from 'docs/src/modules/components/ThemeContext'; @@ -68,7 +69,7 @@ FramedDemo.propTypes = { document: PropTypes.object.isRequired, }; -const Frame = styled('iframe')(({ theme }) => ({ +const Iframe = styled('iframe')(({ theme }) => ({ backgroundColor: theme.palette.background.default, flexGrow: 1, height: 400, @@ -76,7 +77,7 @@ const Frame = styled('iframe')(({ theme }) => ({ boxShadow: theme.shadows[1], })); -function DemoFrame(props) { +function DemoIframe(props) { const { children, name, ...other } = props; /** * @type {import('react').Ref} @@ -104,7 +105,7 @@ function DemoFrame(props) { const document = frameRef.current?.contentDocument; return ( - + + +### Human Interface Guidelines (Apple) + +- Design resource: [Sketch library](https://developer.apple.com/design/resources/) +- Font: [San Francisco (SF)](https://developer.apple.com/fonts/) + + + +### Material Design 3 (Google) + +- Design resource: [Figma](https://www.figma.com/community/file/1035203688168086460) +- Font: [Roboto](https://fonts.google.com/specimen/Roboto) + + + +:::success +Feel free to [submit a PR](https://github.com/mui/material-ui/compare) to add your favorite typography system ❤️. +::: diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index 1833e0c8b49bc6..e002525e5c4f01 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -94,6 +94,7 @@ const pages = [ { pathname: '/joy-ui/customization/dark-mode' }, { pathname: '/joy-ui/customization/default-theme' }, { pathname: '/joy-ui/customization/theme-tokens' }, + { pathname: '/joy-ui/customization/theme-typography' }, { pathname: '/joy-ui/customization/themed-components' }, { pathname: '/joy-ui/customization/using-css-variables', title: 'Using CSS variables' }, ], diff --git a/docs/pages/joy-ui/customization/theme-typography.js b/docs/pages/joy-ui/customization/theme-typography.js new file mode 100644 index 00000000000000..78659441ef5187 --- /dev/null +++ b/docs/pages/joy-ui/customization/theme-typography.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/customization/theme-typography/theme-typography.md?@mui/markdown'; + +export default function Page() { + return ; +} From 4cf3c84d6fa722e843f1a45e76aeb6ef4e9caac7 Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Thu, 17 Nov 2022 12:49:28 +0700 Subject: [PATCH 075/314] [docs] Hotfix missing styles in dark mode (#35179) --- .../src/modules/components/MarkdownElement.js | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/src/modules/components/MarkdownElement.js b/docs/src/modules/components/MarkdownElement.js index b1802eb0c492cc..59841af71cfa10 100644 --- a/docs/src/modules/components/MarkdownElement.js +++ b/docs/src/modules/components/MarkdownElement.js @@ -411,13 +411,17 @@ const Root = styled('div')( }), { ':where(.mode-dark) &': { + color: 'rgb(255, 255, 255)', '& :not(pre) > code': { // inline code block - color: '#fff', + color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`, }, '& strong': { color: `var(--muidocs-palette-grey-200, ${darkTheme.palette.grey[200]})`, }, + '& hr': { + backgroundColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`, + }, '& h1': { color: `var(--muidocs-palette-grey-50, ${darkTheme.palette.grey[50]})`, }, @@ -438,8 +442,12 @@ const Root = styled('div')( }, '& h1, & h2, & h3, & h4': { '&:hover .anchor-link-style': { + color: `var(--muidocs-palette-text-secondary, ${darkTheme.palette.text.secondary})`, background: alpha(darkTheme.palette.primaryDark[800], 0.3), borderColor: `var(--muidocs-palette-primaryDark-500, ${darkTheme.palette.primaryDark[500]})`, + '&:hover': { + color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`, + }, }, }, '& h1 code, & h2 code, & h3 code': { @@ -455,6 +463,17 @@ const Root = styled('div')( '& .prop-type': { color: '#ffb6ec', }, + '& .prop-default': { + borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`, + }, + }, + '& td': { + color: `var(--muidocs-palette-text-secondary, ${darkTheme.palette.text.secondary})`, + borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`, + }, + '& th': { + color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`, + borderColor: `var(--muidocs-palette-divider, ${darkTheme.palette.divider})`, }, '& blockquote': { borderColor: `var(--muidocs-palette-warning-500, ${darkTheme.palette.warning[500]})`, @@ -512,7 +531,7 @@ const Root = styled('div')( color: `var(--muidocs-palette-primary-light, ${darkTheme.palette.primary.light})`, }, '& kbd.key': { - color: '#fff', + color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`, backgroundColor: `var(--muidocs-palette-primaryDark-900, ${darkTheme.palette.primaryDark[900]})`, border: `1px solid var(--muidocs-palette-primaryDark-500, ${darkTheme.palette.primaryDark[500]})`, boxShadow: `inset 0 -1px 0 var(--muidocs-palette-primaryDark-700, ${darkTheme.palette.primaryDark[700]})`, From 1563ccc86145a6f2cfe4aa7b52f2717c3c13bfe5 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Thu, 17 Nov 2022 10:25:31 +0100 Subject: [PATCH 076/314] [typescript] Add `background.defaultChannel` to `CssVarsPalette` (#35174) --- packages/mui-material/src/styles/experimental_extendTheme.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mui-material/src/styles/experimental_extendTheme.d.ts b/packages/mui-material/src/styles/experimental_extendTheme.d.ts index 26f92bae2b9587..5848c35e7f962e 100644 --- a/packages/mui-material/src/styles/experimental_extendTheme.d.ts +++ b/packages/mui-material/src/styles/experimental_extendTheme.d.ts @@ -238,6 +238,7 @@ export interface CssVarsPalette { success: PaletteColorChannel; warning: PaletteColorChannel; text: PaletteTextChannel; + background: PaletteBackgroundChannel; dividerChannel: string; action: PaletteActionChannel; Alert: PaletteAlert; From dd9acb7e5965f0466533c04544ad57d2d5eb9a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Thu, 17 Nov 2022 13:30:28 +0100 Subject: [PATCH 077/314] [docs] Explain how the `error` prop works in the Unstyled Input (#35171) --- .../api-docs/input-unstyled/input-unstyled.json | 2 +- packages/mui-base/src/InputUnstyled/InputUnstyled.tsx | 2 +- packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts | 7 ++++++- packages/mui-base/src/InputUnstyled/useInput.types.ts | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/translations/api-docs/input-unstyled/input-unstyled.json b/docs/translations/api-docs/input-unstyled/input-unstyled.json index 6ae799f39b1dcd..0cd599462e4f73 100644 --- a/docs/translations/api-docs/input-unstyled/input-unstyled.json +++ b/docs/translations/api-docs/input-unstyled/input-unstyled.json @@ -8,7 +8,7 @@ "defaultValue": "The default value. Use when the component is not controlled.", "disabled": "If true, the component is disabled. The prop defaults to the value (false) inherited from the parent FormControl component.", "endAdornment": "Trailing adornment for this input.", - "error": "If true, the input will indicate an error. The prop defaults to the value (false) inherited from the parent FormControl component.", + "error": "If true, the input will indicate an error by setting the aria-invalid attribute on the input and the Mui-error class on the root element. The prop defaults to the value (false) inherited from the parent FormControl component.", "id": "The id of the input element.", "maxRows": "Maximum number of rows to display when multiline option is set to true.", "minRows": "Minimum number of rows to display when multiline option is set to true.", diff --git a/packages/mui-base/src/InputUnstyled/InputUnstyled.tsx b/packages/mui-base/src/InputUnstyled/InputUnstyled.tsx index b3f27987b0e74c..450a9edb1ead57 100644 --- a/packages/mui-base/src/InputUnstyled/InputUnstyled.tsx +++ b/packages/mui-base/src/InputUnstyled/InputUnstyled.tsx @@ -229,7 +229,7 @@ InputUnstyled.propTypes /* remove-proptypes */ = { */ endAdornment: PropTypes.node, /** - * If `true`, the `input` will indicate an error. + * If `true`, the `input` will indicate an error by setting the `aria-invalid` attribute on the input and the `Mui-error` class on the root element. * The prop defaults to the value (`false`) inherited from the parent FormControl component. */ error: PropTypes.bool, diff --git a/packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts b/packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts index d72db2b55524cf..aed95dc42837b6 100644 --- a/packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts +++ b/packages/mui-base/src/InputUnstyled/InputUnstyled.types.ts @@ -57,7 +57,7 @@ export interface MultiLineInputUnstyledProps { } export type InputUnstyledOwnProps = (SingleLineInputUnstyledProps | MultiLineInputUnstyledProps) & - UseInputParameters & { + Omit & { 'aria-describedby'?: string; 'aria-label'?: string; 'aria-labelledby'?: string; @@ -79,6 +79,11 @@ export type InputUnstyledOwnProps = (SingleLineInputUnstyledProps | MultiLineInp * Trailing adornment for this input. */ endAdornment?: React.ReactNode; + /** + * If `true`, the `input` will indicate an error by setting the `aria-invalid` attribute on the input and the `Mui-error` class on the root element. + * The prop defaults to the value (`false`) inherited from the parent FormControl component. + */ + error?: boolean; /** * The id of the `input` element. */ diff --git a/packages/mui-base/src/InputUnstyled/useInput.types.ts b/packages/mui-base/src/InputUnstyled/useInput.types.ts index 3ce8a8835a36dd..297f59f723435a 100644 --- a/packages/mui-base/src/InputUnstyled/useInput.types.ts +++ b/packages/mui-base/src/InputUnstyled/useInput.types.ts @@ -11,7 +11,7 @@ export interface UseInputParameters { */ disabled?: boolean; /** - * If `true`, the `input` will indicate an error. + * If `true`, the `input` will indicate an error by setting the `aria-invalid` attribute. * The prop defaults to the value (`false`) inherited from the parent FormControl component. */ error?: boolean; From 2f27c2eafdf04f278c94a483b76ca6ec925903e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Thu, 17 Nov 2022 13:30:51 +0100 Subject: [PATCH 078/314] [core] Ensure that prettier CI step fails when code is badly formatted (#35170) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 197a61b10fdeb1..eadf09608b7f6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -216,7 +216,7 @@ jobs: - install_js - run: name: '`yarn prettier` changes committed?' - command: yarn prettier check-changed + command: yarn prettier --check - run: name: Generate PropTypes command: yarn proptypes From 764faaf1ed158402c17ead5498defedd73dd317d Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:01:58 -0800 Subject: [PATCH 079/314] [core] Upgrade eslint-config-airbnb-typescript (#34642) Signed-off-by: Jan Potoms <2109932+Janpot@users.noreply.github.com> --- .eslintrc.js | 14 +++- .../dark-mode/IdentifySystemMode.js | 4 +- .../dark-mode/IdentifySystemMode.tsx | 4 +- package.json | 3 +- .../src/Autocomplete/Autocomplete.test.tsx | 60 +++++++------- .../mui-joy/src/styles/ColorInversion.test.js | 16 ++-- .../mui-joy/src/styles/ColorInversion.tsx | 4 +- .../src/styles/CssVarsProvider.test.tsx | 4 +- packages/mui-joy/src/utils/useSlot.test.tsx | 12 +-- packages/mui-lab/src/TreeView/TreeView.js | 4 +- .../src/Accordion/Accordion.test.js | 1 + packages/mui-material/src/Menu/Menu.test.js | 1 + .../src/elementAcceptingRef.test.tsx | 1 + yarn.lock | 79 ++++--------------- 14 files changed, 91 insertions(+), 116 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 69c22f8dab78b4..c41f51e1185310 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,7 @@ module.exports = { extends: [ 'plugin:eslint-plugin-import/recommended', 'plugin:eslint-plugin-import/typescript', + 'eslint-config-airbnb', 'eslint-config-airbnb-typescript', 'eslint-config-prettier', ], @@ -160,8 +161,7 @@ module.exports = { 'react/state-in-constructor': 'off', // stylistic opinion. For conditional assignment we want it outside, otherwise as static 'react/static-property-placement': 'off', - // Currently not in recommended ruleset but catches real bugs. - 'react/no-unstable-nested-components': 'error', + 'no-restricted-syntax': [ // See https://github.com/eslint/eslint/issues/9192 for why it's needed ...baseStyleRules['no-restricted-syntax'], @@ -171,6 +171,15 @@ module.exports = { selector: 'ImportDeclaration[source.value="react"] ImportDefaultSpecifier', }, ], + + // We re-export default in many places, remove when https://github.com/airbnb/javascript/issues/2500 gets resolved + 'no-restricted-exports': 'off', + // Some of these occurences are deliberate and fixing them will break things in repos that use @monorepo dependency + 'import/no-relative-packages': 'off', + // Avoid accidental auto-"fixes" https://github.com/jsx-eslint/eslint-plugin-react/issues/3458 + 'react/no-invalid-html-attribute': 'off', + + 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], }, overrides: [ { @@ -355,6 +364,7 @@ module.exports = { 'react/require-default-props': 'off', 'react/state-in-constructor': 'off', 'react/static-property-placement': 'off', + 'react/function-component-definition': 'off', }, }, { diff --git a/docs/data/joy/customization/dark-mode/IdentifySystemMode.js b/docs/data/joy/customization/dark-mode/IdentifySystemMode.js index c7dc70ca688797..ae398f48ee45bd 100644 --- a/docs/data/joy/customization/dark-mode/IdentifySystemMode.js +++ b/docs/data/joy/customization/dark-mode/IdentifySystemMode.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { CssVarsProvider, useColorScheme } from '@mui/joy/styles'; import Typography from '@mui/joy/Typography'; -const Identifier = () => { +function Identifier() { const { systemMode } = useColorScheme(); const [mounted, setMounted] = React.useState(false); React.useEffect(() => { @@ -32,7 +32,7 @@ const Identifier = () => { mode. ); -}; +} export default function IdentifySystemMode() { return ( diff --git a/docs/data/joy/customization/dark-mode/IdentifySystemMode.tsx b/docs/data/joy/customization/dark-mode/IdentifySystemMode.tsx index c7dc70ca688797..ae398f48ee45bd 100644 --- a/docs/data/joy/customization/dark-mode/IdentifySystemMode.tsx +++ b/docs/data/joy/customization/dark-mode/IdentifySystemMode.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { CssVarsProvider, useColorScheme } from '@mui/joy/styles'; import Typography from '@mui/joy/Typography'; -const Identifier = () => { +function Identifier() { const { systemMode } = useColorScheme(); const [mounted, setMounted] = React.useState(false); React.useEffect(() => { @@ -32,7 +32,7 @@ const Identifier = () => { mode. ); -}; +} export default function IdentifySystemMode() { return ( diff --git a/package.json b/package.json index 6c801c764fd894..6c787f19b7474a 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,8 @@ "dtslint": "^4.2.1", "enzyme": "^3.11.0", "eslint": "^8.27.0", - "eslint-config-airbnb-typescript": "^12.3.1", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-webpack": "^0.13.2", "eslint-plugin-babel": "^5.3.1", diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx index 44dfe3673f1d49..80be88419afb23 100644 --- a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -547,17 +547,19 @@ describe('Joy ', () => { it('should trigger a form expectedly', () => { const handleSubmit = spy(); - const Test = (props: any) => ( -
{ - if (!event.defaultPrevented && event.key === 'Enter') { - handleSubmit(); - } - }} - > - -
- ); + function Test(props: any) { + return ( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + +
+ ); + } const { setProps } = render(); let textbox = screen.getByRole('combobox'); @@ -2031,26 +2033,28 @@ describe('Joy ', () => { it('should prevent the default event handlers', () => { const handleChange = spy(); const handleSubmit = spy(); - const Test = () => ( -
{ - if (!event.defaultPrevented && event.key === 'Enter') { - handleSubmit(); - } - }} - > - { - if (event.key === 'Enter') { - event.defaultMuiPrevented = true; + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); } }} - /> -
- ); + > + { + if (event.key === 'Enter') { + event.defaultMuiPrevented = true; + } + }} + /> + + ); + } render(); const textbox = screen.getByRole('combobox'); fireEvent.keyDown(textbox, { key: 'ArrowDown' }); diff --git a/packages/mui-joy/src/styles/ColorInversion.test.js b/packages/mui-joy/src/styles/ColorInversion.test.js index 2e80d465f97b5d..6da281dde59606 100644 --- a/packages/mui-joy/src/styles/ColorInversion.test.js +++ b/packages/mui-joy/src/styles/ColorInversion.test.js @@ -7,13 +7,15 @@ import ThemeProvider from './ThemeProvider'; import ColorInversion, { useColorInversion } from './ColorInversion'; import { createSolidInversion } from './variantUtils'; -const Parent = ({ children, invertedColors }) => ( - - {children} - -); +const OVERRIDABLE_VARIANT = ['plain', 'outlined', 'soft', 'solid']; + +function Parent({ children, invertedColors }) { + return ( + + {children} + + ); +} const Child = (inProps) => { const props = useThemeProps({ name: 'Child', props: inProps }); diff --git a/packages/mui-joy/src/styles/ColorInversion.tsx b/packages/mui-joy/src/styles/ColorInversion.tsx index 4162d1247ec3ba..cc5fa33a922b49 100644 --- a/packages/mui-joy/src/styles/ColorInversion.tsx +++ b/packages/mui-joy/src/styles/ColorInversion.tsx @@ -29,13 +29,13 @@ interface ColorInversionProviderProps { variant?: VariantProp; } -export const ColorInversionProvider = ({ children, variant }: ColorInversionProviderProps) => { +export function ColorInversionProvider({ children, variant }: ColorInversionProviderProps) { const theme = useSystemTheme(defaultTheme); return ( {children} ); -}; +} export default VariantOverride; diff --git a/packages/mui-joy/src/styles/CssVarsProvider.test.tsx b/packages/mui-joy/src/styles/CssVarsProvider.test.tsx index b5cfd03ba47e80..87879610f54b5e 100644 --- a/packages/mui-joy/src/styles/CssVarsProvider.test.tsx +++ b/packages/mui-joy/src/styles/CssVarsProvider.test.tsx @@ -537,10 +537,10 @@ describe('[Joy] CssVarsProvider', () => { }); it('contain expected colorInversion', function test() { - const Text = () => { + function Text() { const theme = useTheme(); return
{Object.keys(theme.colorInversion).join(',')}
; - }; + } const { container } = render( diff --git a/packages/mui-joy/src/utils/useSlot.test.tsx b/packages/mui-joy/src/utils/useSlot.test.tsx index c3378938daf0f1..e65cd1a9c599a3 100644 --- a/packages/mui-joy/src/utils/useSlot.test.tsx +++ b/packages/mui-joy/src/utils/useSlot.test.tsx @@ -142,7 +142,7 @@ describe('useSlot', () => { }); const ItemOption = styled('div')({}); - const Item = (props: { + function Item(props: { component?: React.ElementType; components?: { root?: React.ElementType; @@ -154,7 +154,7 @@ describe('useSlot', () => { listbox?: SlotComponentProps<'span', Record, {}>; option?: SlotComponentProps<'div', Record, {}>; }; - }) => { + }) { const ref = React.useRef(null); const [SlotRoot, rootProps] = useSlot('root', { ref, @@ -194,7 +194,7 @@ describe('useSlot', () => {
); - }; + } it('should render popper with styled-component', () => { const { getByRole } = render(); @@ -205,9 +205,9 @@ describe('useSlot', () => { }); it('the listbox slot should be replaceable', () => { - const Listbox = ({ component }: { component?: React.ElementType }) => ( -
    - ); + function Listbox({ component }: { component?: React.ElementType }) { + return
      ; + } const { getByRole } = render(); expect(getByRole('list')).toBeVisible(); expect(getByRole('list')).not.to.have.attribute('class'); diff --git a/packages/mui-lab/src/TreeView/TreeView.js b/packages/mui-lab/src/TreeView/TreeView.js index 68d796b1782d79..37017852292bac 100644 --- a/packages/mui-lab/src/TreeView/TreeView.js +++ b/packages/mui-lab/src/TreeView/TreeView.js @@ -792,8 +792,8 @@ const TreeView = React.forwardRef(function TreeView(inProps, ref) { return ( ', () => { Accordion.propTypes, { classes: {}, + // eslint-disable-next-line react/jsx-no-useless-fragment children: , }, 'prop', diff --git a/packages/mui-material/src/Menu/Menu.test.js b/packages/mui-material/src/Menu/Menu.test.js index 90b23467e24f04..d9c9c0b0f91ac6 100644 --- a/packages/mui-material/src/Menu/Menu.test.js +++ b/packages/mui-material/src/Menu/Menu.test.js @@ -249,6 +249,7 @@ describe('', () => { expect(() => { render( + {/* eslint-disable-next-line react/jsx-no-useless-fragment */} , ); diff --git a/packages/mui-utils/src/elementAcceptingRef.test.tsx b/packages/mui-utils/src/elementAcceptingRef.test.tsx index b8cd665a826eda..465721411cebfa 100644 --- a/packages/mui-utils/src/elementAcceptingRef.test.tsx +++ b/packages/mui-utils/src/elementAcceptingRef.test.tsx @@ -103,6 +103,7 @@ describe('elementAcceptingRef', () => { // undesired behavior it('accepts Fragment', () => { + // eslint-disable-next-line react/jsx-no-useless-fragment assertPass(); }); }); diff --git a/yarn.lock b/yarn.lock index 3b8f68a4a9b25e..da9fe07789368c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3916,16 +3916,6 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^4.4.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== - dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" - "@typescript-eslint/parser@^5.41.0": version "5.41.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.41.0.tgz#0414a6405007e463dc527b459af1f19430382d67" @@ -3936,14 +3926,6 @@ "@typescript-eslint/typescript-estree" "5.41.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - "@typescript-eslint/scope-manager@5.41.0": version "5.41.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz#28e3a41d626288d0628be14cf9de8d49fc30fadf" @@ -3962,29 +3944,11 @@ debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== - "@typescript-eslint/types@5.41.0": version "5.41.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.41.0.tgz#6800abebc4e6abaf24cdf220fb4ce28f4ab09a85" integrity sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA== -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.41.0": version "5.41.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz#bf5c6b3138adbdc73ba4871d060ae12c59366c61" @@ -4012,14 +3976,6 @@ eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== - dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" - "@typescript-eslint/visitor-keys@5.41.0": version "5.41.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz#d3510712bc07d5540160ed3c0f8f213b73e3bcd9" @@ -7432,32 +7388,31 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^14.2.0, eslint-config-airbnb-base@^14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" - integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== dependencies: confusing-browser-globals "^1.0.10" object.assign "^4.1.2" - object.entries "^1.1.2" + object.entries "^1.1.5" + semver "^6.3.0" -eslint-config-airbnb-typescript@^12.3.1: - version "12.3.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.3.1.tgz#83ab40d76402c208eb08516260d1d6fac8f8acbc" - integrity sha512-ql/Pe6/hppYuRp4m3iPaHJqkBB7dgeEmGPQ6X0UNmrQOfTF+dXw29/ZjU2kQ6RDoLxaxOA+Xqv07Vbef6oVTWw== +eslint-config-airbnb-typescript@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07" + integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g== dependencies: - "@typescript-eslint/parser" "^4.4.1" - eslint-config-airbnb "^18.2.0" - eslint-config-airbnb-base "^14.2.0" + eslint-config-airbnb-base "^15.0.0" -eslint-config-airbnb@^18.2.0: - version "18.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9" - integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg== +eslint-config-airbnb@^19.0.4: + version "19.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== dependencies: - eslint-config-airbnb-base "^14.2.1" + eslint-config-airbnb-base "^15.0.0" object.assign "^4.1.2" - object.entries "^1.1.2" + object.entries "^1.1.5" eslint-config-prettier@^8.5.0: version "8.5.0" From ee3091faa0d9a9b7ae450e6158bf58e7f1d04f6a Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 17:23:23 +0700 Subject: [PATCH 080/314] use getBy for better DX --- test/utils/describeConformance.js | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index e2e6cc16a3f2c9..007e686e39a021 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -221,7 +221,7 @@ function forEachSlot(slots, callback) { } } -function testSlotsProp(element, getOptions) { +function testSlots(element, getOptions) { const { render, slots } = getOptions(); // eslint-disable-next-line react/prop-types @@ -243,9 +243,9 @@ function testSlotsProp(element, getOptions) { [capitalize(slotName)]: slotComponent, }; - const { queryByTestId } = render(React.cloneElement(element, { components })); - const renderedElement = queryByTestId('custom'); - expect(renderedElement).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { components })); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -264,9 +264,9 @@ function testSlotsProp(element, getOptions) { [slotName]: slotComponent, }; - const { queryByTestId } = render(React.cloneElement(element, { slots: components })); - const renderedElement = queryByTestId('custom'); - expect(renderedElement).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { slots: components })); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -307,12 +307,12 @@ function testSlotsProp(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); - expect(queryByTestId('from-slots')).not.to.equal(null); - expect(queryByTestId('from-components')).to.equal(null); + expect(getByTestId('from-slots')).toBeVisible(); + expect(getByTestId('from-components')).toBeVisible(); }); if (slotOptions.testWithElement !== null) { @@ -335,12 +335,12 @@ function testSlotsProp(element, getOptions) { }, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { components, componentsProps }), ); - const renderedElement = queryByTestId('customized'); - expect(renderedElement).not.to.equal(null); + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -365,12 +365,12 @@ function testSlotsProp(element, getOptions) { }, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { slots: components, slotProps }), ); - const renderedElement = queryByTestId('customized'); - expect(renderedElement).not.to.equal(null); + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -381,7 +381,7 @@ function testSlotsProp(element, getOptions) { }); } -function testSlotPropsProp(element, getOptions) { +function testSlotProps(element, getOptions) { const { render, slots } = getOptions(); if (!render) { @@ -396,9 +396,9 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -412,9 +412,9 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -436,8 +436,8 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = queryByTestId('custom'); + const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = getByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); @@ -848,8 +848,8 @@ const fullSuite = { refForwarding: describeRef, rootClass: testRootClass, reactTestRenderer: testReactTestRenderer, - slotPropsProp: testSlotPropsProp, - slotsProp: testSlotsProp, + slotPropsProp: testSlotProps, + slotsProp: testSlots, themeDefaultProps: testThemeDefaultProps, themeStyleOverrides: testThemeStyleOverrides, themeVariants: testThemeVariants, From 08db62aba846ad7827021eb83f6d0a8b70bc3a02 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:19:54 +0100 Subject: [PATCH 081/314] [docs] Inform that pickers are in X repository (#35189) Co-authored-by: Andrew Cherniavskii --- .../material/guides/pickers-migration/pickers-migration.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/data/material/guides/pickers-migration/pickers-migration.md b/docs/data/material/guides/pickers-migration/pickers-migration.md index 7d8564d7601a2f..ea8509d2061050 100644 --- a/docs/data/material/guides/pickers-migration/pickers-migration.md +++ b/docs/data/material/guides/pickers-migration/pickers-migration.md @@ -2,6 +2,12 @@

      @material-ui/pickers was moved to the @mui/lab.

      +:::info +**Stable package available**: The pickers are not available in `@mui/lab`after `v5.0.0-alpha.89`. +They have been moved from `@mui/lab` to the MUI X packages `@mui/x-date-pickers` and `@mui/x-date-pickers-pro`. +To migrate from `@mui/lab` to `@mui/x-date-pickers` you can follow the dedicated [migration guide](/x/react-date-pickers/migration-lab/). +::: + :::warning **⚠️ The date picker components were rewritten**. In most places, the logic was rewritten from scratch, so it isn't possible to maintain the whole list of changes. Here's an overview of the most important concepts that were changed. If you are going to upgrade, the easiest way might be to go through each picker usage in your codebase, and rewrite them one at a time. Don't forget to run your tests after each! ::: From ae5629cc3e008e5a8a5e42df92832ec62e89c021 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 19:43:40 +0700 Subject: [PATCH 082/314] move Material UI components test --- test/utils/describeConformance.js | 160 ++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 7 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 007e686e39a021..aa390d23ddd966 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -207,6 +207,12 @@ export function testReactTestRenderer(element) { }); } +/** + * + * @param {object} slots + * @param {(slotName: string, slotOptions: object) => void} callback + * @returns + */ function forEachSlot(slots, callback) { if (!slots) { return; @@ -307,12 +313,12 @@ function testSlots(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { getByTestId } = render( + const { getByTestId, queryByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); expect(getByTestId('from-slots')).toBeVisible(); - expect(getByTestId('from-components')).toBeVisible(); + expect(queryByTestId('from-components')).to.equal(null); }); if (slotOptions.testWithElement !== null) { @@ -476,9 +482,13 @@ function testSlotProps(element, getOptions) { * @property {object} [testVariantProps] * @property {(mount: (node: React.ReactNode) => import('enzyme').ReactWrapper) => (node: React.ReactNode) => import('enzyme').ReactWrapper} [wrapMount] - You can use this option to mount the component with enzyme in a WrapperComponent. Make sure the returned node corresponds to the input node and not the wrapper component. * @property {boolean} [testCustomVariant] - The component supports custom variant + * @property {object} components - Material UI's components prop + * @property {object} slots - MUI slots prop */ /** + * >>> This will be DEPRECEATED in v6 <<< + * * MUI components have a `components` prop that allows rendering a different * Components from @inheritComponent * @param {React.ReactElement} element @@ -486,13 +496,149 @@ function testSlotProps(element, getOptions) { */ function testComponentsProp(element, getOptions) { describe('prop components:', () => { - it('can render another root component with the `components` prop', () => { - const { mount, testComponentsRootPropWith: component = 'em' } = getOptions(); + const { components, slots, render } = getOptions(); - const wrapper = mount(React.cloneElement(element, { components: { Root: component } })); + if (!components) { + // the tests below will cover all the components + it('can render another root component with the `components` prop', () => { + const { mount, testComponentsRootPropWith: component = 'em' } = getOptions(); + const wrapper = mount(React.cloneElement(element, { components: { Root: component } })); - expect(findRootComponent(wrapper, { component }).exists()).to.equal(true); - }); + expect(findRootComponent(wrapper, { component }).exists()).to.equal(true); + }); + } else { + // eslint-disable-next-line react/prop-types + const CustomComponent = React.forwardRef(({ className }, ref) => ( + + )); + + forEachSlot(components, (slotName, slotOptions) => { + it(`allows overriding the ${slotName} slot with a component using the components.${capitalize( + slotName, + )} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotComponent = slotOptions.testWithComponent ?? CustomComponent; + + const { getByTestId } = render( + React.cloneElement(element, { components: { [capitalize(slotName)]: slotComponent } }), + ); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`prioritizes the 'slots.${slotName}' over components.${capitalize( + slotName, + )} if both are defined`, () => { + if (!slots || !slots[slotName]) { + throw new Error( + `missing "slots" in options, unable to test "slots" overrides "components" for this Material UI component`, + ); + } + + // eslint-disable-next-line react/prop-types + const ComponentForComponentsProp = React.forwardRef(({ children }, ref) => { + const SlotComponent = slotOptions.testWithComponent ?? 'div'; + return ( + + {children} + + ); + }); + + // eslint-disable-next-line react/prop-types + const ComponentForSlotsProp = React.forwardRef(({ children }, ref) => { + const SlotComponent = slots[slotName].testWithComponent ?? 'div'; + return ( + + {children} + + ); + }); + + const { getByTestId, queryByTestId } = render( + React.cloneElement(element, { + components: { + [capitalize(slotName)]: ComponentForComponentsProp, + }, + slots: { + [slotName]: ComponentForSlotsProp, + }, + }), + ); + + expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-components')).to.equal(null); + }); + + if (slotOptions.testWithElement !== null) { + it(`allows overriding the ${slotName} slot with an element using the components.${capitalize( + slotName, + )} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotElement = slotOptions.testWithElement ?? 'i'; + + const { getByTestId } = render( + React.cloneElement(element, { + components: { + [capitalize(slotName)]: slotElement, + }, + componentsProps: { + [slotName]: { + 'data-testid': 'customized', + }, + }, + }), + ); + + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); + + expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`allows overriding the ${slotName} slot with an element using the slots.${slotName} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotElement = slotOptions.testWithElement ?? 'i'; + + const { getByTestId } = render( + React.cloneElement(element, { + slots: { + [slotName]: slotElement, + }, + slotProps: { + [slotName]: { + 'data-testid': 'customized', + }, + }, + }), + ); + + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); + + expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + } + }); + } }); } From 52df00949ada19acebe9cb8f13e8b9cee7e89faf Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 19:47:53 +0700 Subject: [PATCH 083/314] fix Alert --- packages/mui-material/src/Alert/Alert.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/mui-material/src/Alert/Alert.test.js b/packages/mui-material/src/Alert/Alert.test.js index cecd3619b94a4e..476b0eebdb1659 100644 --- a/packages/mui-material/src/Alert/Alert.test.js +++ b/packages/mui-material/src/Alert/Alert.test.js @@ -18,11 +18,14 @@ describe('', () => { muiName: 'MuiAlert', testVariantProps: { variant: 'standard', color: 'success' }, testDeepOverrides: { slotName: 'message', slotClassName: classes.message }, + components: { + closeButton: {}, + closeIcon: {}, + }, slots: { closeButton: {}, closeIcon: {}, }, - skip: ['componentsProp'], })); describe('prop: square', () => { From 397c96d9c2bc27595e0e30a3974e49ff19bc4e57 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:36:19 +0700 Subject: [PATCH 084/314] update conformance --- test/utils/describeConformance.js | 94 ++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index aa390d23ddd966..9911219f10b73b 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -642,6 +642,87 @@ function testComponentsProp(element, getOptions) { }); } +function testComponentsPropsProp(element, getOptions) { + const { render, components } = getOptions(); + + if (!render) { + throwMissingPropError('render'); + } + + describe('prop componentsProps:', () => { + forEachSlot(components, (slotName, slotOptions) => { + it(`sets custom properties on the ${slotName} slot's element with the componentsProps.${slotName} prop`, () => { + const componentsProps = { + [slotName]: { + 'data-testid': 'custom', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); + + if (slotOptions.expectedClassName) { + expect(slotComponent).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} prop`, () => { + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); + + if (slotOptions.expectedClassName) { + expect(slotComponent).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`prioritizes the 'slotProps.${slotName}' over componentsProps.${slotName} if both are defined`, () => { + const componentsProps = { + [slotName]: { + 'data-testid': 'custom', + 'data-from-components-props': 'true', + }, + }; + + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + 'data-from-slot-props': 'true', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); + expect(slotComponent).not.to.have.attribute('data-from-components-props'); + }); + + if (slotOptions.expectedClassName) { + it(`merges the class names provided in slotsProps.${slotName} with the built-in ones`, () => { + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + className: randomStringValue(), + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + + expect(getByTestId('custom')).to.have.class(slotOptions.expectedClassName); + expect(getByTestId('custom')).to.have.class(slotProps[slotName].className); + }); + } + }); + }); +} + /** * MUI theme has a components section that allows specifying default props. * Components from @inheritComponent @@ -989,6 +1070,7 @@ function testThemeVariants(element, getOptions) { const fullSuite = { componentProp: testComponentProp, componentsProp: testComponentsProp, + componentsPropsProp: testComponentsPropsProp, mergeClassName: testClassName, propsSpread: testPropsSpread, refForwarding: describeRef, @@ -1013,6 +1095,7 @@ export default function describeConformance(minimalElement, getOptions) { after: runAfterHook = () => {}, only = Object.keys(fullSuite), slots, + components, skip = [], wrapMount, } = getOptions(); @@ -1021,11 +1104,16 @@ export default function describeConformance(minimalElement, getOptions) { (testKey) => only.indexOf(testKey) !== -1 && skip.indexOf(testKey) === -1, ); - const slotBasedTests = ['slotsProp', 'slotPropsProp']; - if (!slots) { // if `slots` are not defined, do not run tests that depend on them - filteredTests = filteredTests.filter((testKey) => !slotBasedTests.includes(testKey)); + filteredTests = filteredTests.filter( + (testKey) => !['slotsProp', 'slotPropsProp'].includes(testKey), + ); + } + + if (!components) { + // if `components` are not defined, do not run tests that depend on them + filteredTests = filteredTests.filter((testKey) => !['componentsPropsProp'].includes(testKey)); } const baseMount = createMount(); From 842cf9b7074139018f2f17eb5d890e99386da9a1 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:37:05 +0700 Subject: [PATCH 085/314] Revert "use getBy for better DX" This reverts commit ee3091faa0d9a9b7ae450e6158bf58e7f1d04f6a. --- test/utils/describeConformance.js | 52 +++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 9911219f10b73b..e73ef3911c5f09 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -227,7 +227,7 @@ function forEachSlot(slots, callback) { } } -function testSlots(element, getOptions) { +function testSlotsProp(element, getOptions) { const { render, slots } = getOptions(); // eslint-disable-next-line react/prop-types @@ -249,9 +249,9 @@ function testSlots(element, getOptions) { [capitalize(slotName)]: slotComponent, }; - const { getByTestId } = render(React.cloneElement(element, { components })); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { components })); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -270,9 +270,9 @@ function testSlots(element, getOptions) { [slotName]: slotComponent, }; - const { getByTestId } = render(React.cloneElement(element, { slots: components })); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slots: components })); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -313,11 +313,11 @@ function testSlots(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { getByTestId, queryByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); - expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-slots')).not.to.equal(null); expect(queryByTestId('from-components')).to.equal(null); }); @@ -341,12 +341,12 @@ function testSlots(element, getOptions) { }, }; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components, componentsProps }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -371,12 +371,12 @@ function testSlots(element, getOptions) { }, }; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { slots: components, slotProps }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -387,7 +387,7 @@ function testSlots(element, getOptions) { }); } -function testSlotProps(element, getOptions) { +function testSlotPropsProp(element, getOptions) { const { render, slots } = getOptions(); if (!render) { @@ -402,9 +402,9 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -418,9 +418,9 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -442,8 +442,8 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = getByTestId('custom'); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = queryByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); @@ -1076,8 +1076,8 @@ const fullSuite = { refForwarding: describeRef, rootClass: testRootClass, reactTestRenderer: testReactTestRenderer, - slotPropsProp: testSlotProps, - slotsProp: testSlots, + slotPropsProp: testSlotPropsProp, + slotsProp: testSlotsProp, themeDefaultProps: testThemeDefaultProps, themeStyleOverrides: testThemeStyleOverrides, themeVariants: testThemeVariants, From fc8a694213eca9ab69637aabc91578cc15f51834 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:43:48 +0700 Subject: [PATCH 086/314] update Autocomplete --- .../src/Autocomplete/Autocomplete.test.js | 6 +++ test/utils/describeConformance.js | 40 ++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index f4282ecdfe31b0..8cab5bab7632eb 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -55,6 +55,12 @@ describe('', () => { testStateOverrides: { prop: 'fullWidth', value: true, styleKey: 'fullWidth' }, refInstanceof: window.HTMLDivElement, testComponentPropWith: 'div', + components: { + clearIndicator: { expectedClassName: classes.clearIndicator }, + paper: { expectedClassName: classes.paper }, + popper: { expectedClassName: classes.popper }, + popupIndicator: { expectedClassName: classes.popupIndicator }, + }, slots: { clearIndicator: { expectedClassName: classes.clearIndicator }, paper: { expectedClassName: classes.paper }, diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index e73ef3911c5f09..fba2aefb7d1aba 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -522,11 +522,11 @@ function testComponentsProp(element, getOptions) { const slotComponent = slotOptions.testWithComponent ?? CustomComponent; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: slotComponent } }), ); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -561,7 +561,7 @@ function testComponentsProp(element, getOptions) { ); }); - const { getByTestId, queryByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: ComponentForComponentsProp, @@ -572,7 +572,7 @@ function testComponentsProp(element, getOptions) { }), ); - expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-slots')).not.to.equal(null); expect(queryByTestId('from-components')).to.equal(null); }); @@ -586,7 +586,7 @@ function testComponentsProp(element, getOptions) { const slotElement = slotOptions.testWithElement ?? 'i'; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: slotElement, @@ -599,8 +599,8 @@ function testComponentsProp(element, getOptions) { }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -615,7 +615,7 @@ function testComponentsProp(element, getOptions) { const slotElement = slotOptions.testWithElement ?? 'i'; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { slots: { [slotName]: slotElement, @@ -628,8 +628,8 @@ function testComponentsProp(element, getOptions) { }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -658,9 +658,9 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -674,9 +674,9 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -698,8 +698,10 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = getByTestId('custom'); + const { queryByTestId } = render( + React.cloneElement(element, { componentsProps, slotProps }), + ); + const slotComponent = queryByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); From d59f395e71df0d9a2cfda91ed5031f2af1af71a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Fri, 18 Nov 2022 14:46:42 +0100 Subject: [PATCH 087/314] [Select][base] Add attributes to conform with ARIA 1.2 (#35182) --- .../MultiSelectUnstyled.test.tsx | 138 +++++++++++--- .../SelectUnstyled/SelectUnstyled.test.tsx | 176 +++++++++++++----- .../mui-base/src/SelectUnstyled/useSelect.ts | 6 +- .../test/integration/SelectUnstyled.test.tsx | 2 +- .../src/FormControl/FormControl.test.tsx | 2 +- packages/mui-joy/src/Select/Select.test.tsx | 44 ++--- 6 files changed, 274 insertions(+), 94 deletions(-) diff --git a/packages/mui-base/src/MultiSelectUnstyled/MultiSelectUnstyled.test.tsx b/packages/mui-base/src/MultiSelectUnstyled/MultiSelectUnstyled.test.tsx index 0efd2271ef89dd..1a00afc9697870 100644 --- a/packages/mui-base/src/MultiSelectUnstyled/MultiSelectUnstyled.test.tsx +++ b/packages/mui-base/src/MultiSelectUnstyled/MultiSelectUnstyled.test.tsx @@ -12,6 +12,7 @@ import { userEvent, act, fireEvent, + screen, } from 'test/utils'; describe('MultiSelectUnstyled', () => { @@ -53,13 +54,13 @@ describe('MultiSelectUnstyled', () => { it(`opens the dropdown when the "${key}" key is down on the button`, () => { // can't use the default native `button` as it doesn't treat enter or space press as a click const { getByRole } = render(); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.focus(); + select.focus(); }); - fireEvent.keyDown(button, { key }); + fireEvent.keyDown(select, { key }); - expect(button).to.have.attribute('aria-expanded', 'true'); + expect(select).to.have.attribute('aria-expanded', 'true'); expect(getByRole('listbox')).not.to.equal(null); expect(document.activeElement).to.equal(getByRole('listbox')); }); @@ -68,13 +69,13 @@ describe('MultiSelectUnstyled', () => { it(`opens the dropdown when the " " key is let go on the button`, () => { // can't use the default native `button` as it doesn't treat enter or space press as a click const { getByRole } = render(); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.focus(); + select.focus(); }); - fireEvent.keyUp(button, { key: ' ' }); + fireEvent.keyUp(select, { key: ' ' }); - expect(button).to.have.attribute('aria-expanded', 'true'); + expect(select).to.have.attribute('aria-expanded', 'true'); expect(getByRole('listbox')).not.to.equal(null); expect(document.activeElement).to.equal(getByRole('listbox')); }); @@ -89,9 +90,9 @@ describe('MultiSelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -100,7 +101,7 @@ describe('MultiSelectUnstyled', () => { userEvent.keyPress(listbox, { key: 'ArrowDown' }); // highlights '2' userEvent.keyPress(listbox, { key }); - expect(button).to.have.text('2'); + expect(select).to.have.text('2'); }), ); }); @@ -113,18 +114,18 @@ describe('MultiSelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); userEvent.keyPress(listbox, { key: 'ArrowDown' }); // highlights '2' userEvent.keyPress(listbox, { key: 'Escape' }); - expect(button).to.have.attribute('aria-expanded', 'false'); - expect(button).to.have.text('1'); + expect(select).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.text('1'); expect(queryByRole('listbox')).to.equal(null); }); }); @@ -243,9 +244,9 @@ describe('MultiSelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const optionTwo = getByText('Two'); @@ -299,7 +300,7 @@ describe('MultiSelectUnstyled', () => { , ); - expect(getByRole('button')).to.have.text('One (1), Two (2)'); + expect(getByRole('combobox')).to.have.text('One (1), Two (2)'); }); it('renders the selected values as comma-separated list of labels if renderValue is not provided', () => { @@ -310,7 +311,94 @@ describe('MultiSelectUnstyled', () => { , ); - expect(getByRole('button')).to.have.text('One, Two'); + expect(getByRole('combobox')).to.have.text('One, Two'); + }); + }); + + // according to WAI-ARIA 1.2 (https://www.w3.org/TR/wai-aria-1.2/#combobox) + describe('a11y attributes', () => { + it('should have the `combobox` role', () => { + render( + + One + , + ); + + expect(screen.queryByRole('combobox')).not.to.equal(null); + }); + + it('should have the aria-haspopup listbox', () => { + render( + + One + , + ); + + expect(screen.getByRole('combobox')).to.have.attribute('aria-haspopup', 'listbox'); + }); + + it('should have the aria-expanded attribute', () => { + render( + + One + , + ); + + expect(screen.getByRole('combobox')).to.have.attribute('aria-expanded', 'false'); + }); + + it('should have the aria-expanded attribute set to true when the listbox is open', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + act(() => { + select.click(); + }); + + expect(select).to.have.attribute('aria-expanded', 'true'); + }); + + it('should have the aria-controls attribute', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + + act(() => { + select.click(); + }); + + const listbox = screen.getByRole('listbox'); + const listboxId = listbox.getAttribute('id'); + expect(listboxId).not.to.equal(null); + + expect(select).to.have.attribute('aria-controls', listboxId!); + }); + + it('should have the aria-activedescendant attribute', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + act(() => { + select.click(); + }); + + const listbox = screen.getByRole('listbox'); + fireEvent.keyDown(listbox, { key: 'ArrowDown' }); + + const options = screen.getAllByRole('option'); + expect(listbox).to.have.attribute('aria-activedescendant', options[0].getAttribute('id')!); }); }); @@ -365,10 +453,10 @@ describe('MultiSelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -379,8 +467,8 @@ describe('MultiSelectUnstyled', () => { focusTarget.focus(); }); - expect(button).to.have.attribute('aria-expanded', 'false'); - expect(button).to.have.text('1'); + expect(select).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.text('1'); }); it('focuses the listbox after it is opened', () => { @@ -390,9 +478,9 @@ describe('MultiSelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); expect(document.activeElement).to.equal(getByRole('listbox')); diff --git a/packages/mui-base/src/SelectUnstyled/SelectUnstyled.test.tsx b/packages/mui-base/src/SelectUnstyled/SelectUnstyled.test.tsx index c8377d2e2c102c..2e6b513871a31a 100644 --- a/packages/mui-base/src/SelectUnstyled/SelectUnstyled.test.tsx +++ b/packages/mui-base/src/SelectUnstyled/SelectUnstyled.test.tsx @@ -14,6 +14,7 @@ import { fireEvent, userEvent, act, + screen, } from 'test/utils'; describe('SelectUnstyled', () => { @@ -55,13 +56,13 @@ describe('SelectUnstyled', () => { it(`opens the dropdown when the "${key}" key is down on the button`, () => { // can't use the default native `button` as it doesn't treat enter or space press as a click const { getByRole } = render(); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.focus(); + select.focus(); }); - fireEvent.keyDown(button, { key }); + fireEvent.keyDown(select, { key }); - expect(button).to.have.attribute('aria-expanded', 'true'); + expect(select).to.have.attribute('aria-expanded', 'true'); expect(getByRole('listbox')).not.to.equal(null); expect(document.activeElement).to.equal(getByRole('listbox')); }); @@ -70,13 +71,13 @@ describe('SelectUnstyled', () => { it(`opens the dropdown when the " " key is let go on the button`, () => { // can't use the default native `button` as it doesn't treat enter or space press as a click const { getByRole } = render(); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.focus(); + select.focus(); }); - fireEvent.keyUp(button, { key: ' ' }); + fireEvent.keyUp(select, { key: ' ' }); - expect(button).to.have.attribute('aria-expanded', 'true'); + expect(select).to.have.attribute('aria-expanded', 'true'); expect(getByRole('listbox')).not.to.equal(null); expect(document.activeElement).to.equal(getByRole('listbox')); }); @@ -88,15 +89,15 @@ describe('SelectUnstyled', () => { 1 , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); userEvent.keyPress(listbox, { key }); - expect(button).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.attribute('aria-expanded', 'false'); expect(queryByRole('listbox')).to.equal(null); }); }); @@ -111,9 +112,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -122,7 +123,7 @@ describe('SelectUnstyled', () => { userEvent.keyPress(listbox, { key: 'ArrowDown' }); // highlights '2' userEvent.keyPress(listbox, { key }); - expect(button).to.have.text('2'); + expect(select).to.have.text('2'); }), ); }); @@ -140,9 +141,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -167,9 +168,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -208,9 +209,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -238,9 +239,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -264,9 +265,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -290,9 +291,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -322,18 +323,18 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); userEvent.keyPress(listbox, { key: 'ArrowDown' }); // highlights '2' userEvent.keyPress(listbox, { key: 'Escape' }); - expect(button).to.have.attribute('aria-expanded', 'false'); - expect(button).to.have.text('1'); + expect(select).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.text('1'); }); }); @@ -464,9 +465,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const optionTwo = getByText('Two'); @@ -493,7 +494,7 @@ describe('SelectUnstyled', () => { , ); - expect(getByRole('button')).to.have.text('One (1)'); + expect(getByRole('combobox')).to.have.text('One (1)'); }); it('renders the selected values as a label if renderValue is not provided', () => { @@ -504,7 +505,94 @@ describe('SelectUnstyled', () => { , ); - expect(getByRole('button')).to.have.text('One'); + expect(getByRole('combobox')).to.have.text('One'); + }); + }); + + // according to WAI-ARIA 1.2 (https://www.w3.org/TR/wai-aria-1.2/#combobox) + describe('a11y attributes', () => { + it('should have the `combobox` role', () => { + render( + + One + , + ); + + expect(screen.queryByRole('combobox')).not.to.equal(null); + }); + + it('should have the aria-haspopup listbox', () => { + render( + + One + , + ); + + expect(screen.getByRole('combobox')).to.have.attribute('aria-haspopup', 'listbox'); + }); + + it('should have the aria-expanded attribute', () => { + render( + + One + , + ); + + expect(screen.getByRole('combobox')).to.have.attribute('aria-expanded', 'false'); + }); + + it('should have the aria-expanded attribute set to true when the listbox is open', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + act(() => { + select.click(); + }); + + expect(select).to.have.attribute('aria-expanded', 'true'); + }); + + it('should have the aria-controls attribute', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + + act(() => { + select.click(); + }); + + const listbox = screen.getByRole('listbox'); + const listboxId = listbox.getAttribute('id'); + expect(listboxId).not.to.equal(null); + + expect(select).to.have.attribute('aria-controls', listboxId!); + }); + + it('should have the aria-activedescendant attribute', () => { + render( + + One + , + ); + + const select = screen.getByRole('combobox'); + act(() => { + select.click(); + }); + + const listbox = screen.getByRole('listbox'); + fireEvent.keyDown(listbox, { key: 'ArrowDown' }); + + const options = screen.getAllByRole('option'); + expect(listbox).to.have.attribute('aria-activedescendant', options[0].getAttribute('id')!); }); }); @@ -521,10 +609,10 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const listbox = getByRole('listbox'); @@ -535,8 +623,8 @@ describe('SelectUnstyled', () => { focusTarget.focus(); }); - expect(button).to.have.attribute('aria-expanded', 'false'); - expect(button).to.have.text('1'); + expect(select).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.text('1'); }); it('closes the listbox when already selected option is selected again with a click', () => { @@ -549,17 +637,17 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); const selectedOption = getByTestId('selected-option'); fireEvent.click(selectedOption); - expect(button).to.have.attribute('aria-expanded', 'false'); - expect(button).to.have.text('1'); + expect(select).to.have.attribute('aria-expanded', 'false'); + expect(select).to.have.text('1'); }); it('focuses the listbox after it is opened', () => { @@ -569,9 +657,9 @@ describe('SelectUnstyled', () => { , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.click(); + select.click(); }); expect(document.activeElement).to.equal(getByRole('listbox')); diff --git a/packages/mui-base/src/SelectUnstyled/useSelect.ts b/packages/mui-base/src/SelectUnstyled/useSelect.ts index 03790c4af235df..34606663cdb63a 100644 --- a/packages/mui-base/src/SelectUnstyled/useSelect.ts +++ b/packages/mui-base/src/SelectUnstyled/useSelect.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { unstable_useControlled as useControlled, unstable_useForkRef as useForkRef, + unstable_useId as useId, } from '@mui/utils'; import { useButton } from '../ButtonUnstyled'; import { @@ -32,7 +33,7 @@ function useSelect(props: UseSelectParameters) { buttonRef: buttonRefProp, defaultValue, disabled = false, - listboxId, + listboxId: listboxIdProp, listboxRef: listboxRefProp, multiple = false, onChange, @@ -47,6 +48,7 @@ function useSelect(props: UseSelectParameters) { const handleButtonRef = useForkRef(buttonRefProp, buttonRef); const listboxRef = React.useRef(null); + const listboxId = useId(listboxIdProp); const [value, setValue] = useControlled({ controlled: valueProp, @@ -262,8 +264,10 @@ function useSelect(props: UseSelectParameters) { onMouseDown: createHandleMouseDown(otherHandlers), onKeyDown: createHandleButtonKeyDown(otherHandlers), }), + role: 'combobox' as const, 'aria-expanded': open, 'aria-haspopup': 'listbox' as const, + 'aria-controls': listboxId, }; }; diff --git a/packages/mui-base/test/integration/SelectUnstyled.test.tsx b/packages/mui-base/test/integration/SelectUnstyled.test.tsx index 3480e04462c295..f8e177b2fd2ffa 100644 --- a/packages/mui-base/test/integration/SelectUnstyled.test.tsx +++ b/packages/mui-base/test/integration/SelectUnstyled.test.tsx @@ -40,7 +40,7 @@ describe(' integration', () => { , ); - const select = getByRole('button'); + const select = getByRole('combobox'); act(() => { select.focus(); diff --git a/packages/mui-joy/src/FormControl/FormControl.test.tsx b/packages/mui-joy/src/FormControl/FormControl.test.tsx index f89bdfe390ac62..d75a0b75e41332 100644 --- a/packages/mui-joy/src/FormControl/FormControl.test.tsx +++ b/packages/mui-joy/src/FormControl/FormControl.test.tsx @@ -162,7 +162,7 @@ describe('', () => { ); const label = container.querySelector('label'); - expect(getByRole('button')).to.have.attribute('aria-labelledby', label?.id); + expect(getByRole('combobox')).to.have.attribute('aria-labelledby', label?.id); }); it('should inherit color prop from FormControl', () => { diff --git a/packages/mui-joy/src/Select/Select.test.tsx b/packages/mui-joy/src/Select/Select.test.tsx index d8628b0016acf1..58c61a1e5e0510 100644 --- a/packages/mui-joy/src/Select/Select.test.tsx +++ b/packages/mui-joy/src/Select/Select.test.tsx @@ -35,7 +35,7 @@ describe('Joy , ); - expect(screen.getByRole('button')).to.have.text('Ten'); + expect(screen.getByRole('combobox')).to.have.text('Ten'); }); specify('the trigger is in tab order', () => { @@ -45,7 +45,7 @@ describe('Joy , ); - expect(getByRole('button')).to.have.property('tabIndex', 0); + expect(getByRole('combobox')).to.have.property('tabIndex', 0); }); it('should accept null child', () => { @@ -72,13 +72,13 @@ describe('Joy , ); - const button = getByRole('button'); + const select = getByRole('combobox'); act(() => { - button.focus(); + select.focus(); }); act(() => { - button.blur(); + select.blur(); }); expect(handleBlur.callCount).to.equal(1); @@ -106,7 +106,7 @@ describe('Joy ); - fireEvent.keyDown(getByRole('button'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByRole('combobox'), { key: 'ArrowDown' }); expect(getByRole('listbox')).toHaveFocus(); }); @@ -121,7 +121,7 @@ describe('Joy , ); - fireEvent.click(getByRole('button')); + fireEvent.click(getByRole('combobox')); act(() => { getAllByRole('option')[1].click(); }); @@ -139,7 +139,7 @@ describe('Joy , ); - fireEvent.click(getByRole('button')); + fireEvent.click(getByRole('combobox')); act(() => { getAllByRole('option')[1].click(); }); @@ -151,7 +151,7 @@ describe('Joy ); - expect(getByRole('button', { hidden: true })).to.have.attribute('aria-expanded', 'true'); + expect(getByRole('combobox', { hidden: true })).to.have.attribute('aria-expanded', 'true'); }); }); @@ -214,7 +214,7 @@ describe('Joy , ); - expect(getByRole('button')).to.have.text('Twenty'); + expect(getByRole('combobox')).to.have.text('Twenty'); }); }); @@ -270,32 +270,32 @@ describe('Joy ); - expect(getByRole('button', { hidden: true })).to.have.attribute('aria-expanded', 'true'); + expect(getByRole('combobox', { hidden: true })).to.have.attribute('aria-expanded', 'true'); }); specify('ARIA 1.2: aria-expanded="false" if the listbox isnt displayed', () => { const { getByRole } = render(); - // expect(getByRole('button')).to.have.attribute('aria-disabled', 'true'); + // expect(getByRole('combobox')).to.have.attribute('aria-disabled', 'true'); // }); specify('aria-disabled is not present if component is not disabled', () => { const { getByRole } = render(); - expect(getByRole('button')).to.have.attribute('aria-haspopup', 'listbox'); + expect(getByRole('combobox')).to.have.attribute('aria-haspopup', 'listbox'); }); it('renders an element with listbox behavior', () => { @@ -369,7 +369,7 @@ describe('Joy ); - expect(getByRole('button')).not.to.have.attribute('aria-labelledby'); + expect(getByRole('combobox')).not.to.have.attribute('aria-labelledby'); }); specify('the list of options is not labelled by default', () => { @@ -386,7 +386,7 @@ describe('Joy ', () => { , ); - expect(getByRole('button')).to.have.text('0b100'); + expect(getByRole('combobox')).to.have.text('0b100'); }); }); @@ -410,7 +410,7 @@ describe('Joy ); - expect(getByRole('button')).not.to.have.attribute('id'); + expect(getByRole('combobox')).not.to.have.attribute('id'); }); }); @@ -479,7 +479,7 @@ describe('Joy ', () => { getByTestId('test-element').click(); }); - expect(getByRole('button', { hidden: true })).to.have.attribute('aria-expanded', 'true'); + expect(getByRole('combobox', { hidden: true })).to.have.attribute('aria-expanded', 'true'); }); it('should not show dropdown if stop propagation is handled', () => { @@ -632,7 +632,7 @@ describe('Joy ', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - slots: { + components: { // can't test with DOM element as Input places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/InputBase/InputBase.test.js b/packages/mui-material/src/InputBase/InputBase.test.js index eb1da1530c47fe..4aa9022f88522e 100644 --- a/packages/mui-material/src/InputBase/InputBase.test.js +++ b/packages/mui-material/src/InputBase/InputBase.test.js @@ -21,7 +21,7 @@ describe('', () => { refInstanceof: window.HTMLDivElement, muiName: 'MuiInputBase', testVariantProps: { size: 'small' }, - slots: { + components: { // can't test with DOM element as InputBase places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/ListItem/ListItem.test.js b/packages/mui-material/src/ListItem/ListItem.test.js index c58df6f37daea7..4e60a346d537b9 100644 --- a/packages/mui-material/src/ListItem/ListItem.test.js +++ b/packages/mui-material/src/ListItem/ListItem.test.js @@ -22,7 +22,7 @@ describe('', () => { refInstanceof: window.HTMLLIElement, muiName: 'MuiListItem', testVariantProps: { dense: true }, - slots: { + components: { root: {}, }, skip: ['componentsProp'], diff --git a/packages/mui-material/src/Modal/Modal.test.js b/packages/mui-material/src/Modal/Modal.test.js index 70ea0e748f230b..1ea9daf0b30a8e 100644 --- a/packages/mui-material/src/Modal/Modal.test.js +++ b/packages/mui-material/src/Modal/Modal.test.js @@ -32,7 +32,7 @@ describe('', () => { muiName: 'MuiModal', refInstanceof: window.HTMLDivElement, testVariantProps: { hideBackdrop: true }, - slots: { + components: { root: { expectedClassName: classes.root }, backdrop: {}, }, diff --git a/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js b/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js index 7db2485969817b..509430afd50aaa 100644 --- a/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js +++ b/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js @@ -17,7 +17,7 @@ describe('', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - slots: { + components: { // can't test with DOM element as InputBase places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/PaginationItem/PaginationItem.test.js b/packages/mui-material/src/PaginationItem/PaginationItem.test.js index afc9f71aeac12c..ad7f89d4a0278f 100644 --- a/packages/mui-material/src/PaginationItem/PaginationItem.test.js +++ b/packages/mui-material/src/PaginationItem/PaginationItem.test.js @@ -14,7 +14,7 @@ describe('', () => { refInstanceof: window.HTMLButtonElement, testVariantProps: { variant: 'foo' }, testStateOverrides: { prop: 'variant', value: 'outlined', styleKey: 'outlined' }, - slots: { + components: { first: {}, last: {}, previous: {}, diff --git a/packages/mui-material/src/Popper/Popper.test.js b/packages/mui-material/src/Popper/Popper.test.js index 531c1e94388e61..edf6fbbdf0fc31 100644 --- a/packages/mui-material/src/Popper/Popper.test.js +++ b/packages/mui-material/src/Popper/Popper.test.js @@ -25,7 +25,7 @@ describe('', () => { inheritComponent: 'div', render, refInstanceof: window.HTMLDivElement, - slots: { + components: { root: {}, }, skip: [ diff --git a/packages/mui-material/src/Slider/Slider.test.js b/packages/mui-material/src/Slider/Slider.test.js index b448740edeaa52..29709039aed0e5 100644 --- a/packages/mui-material/src/Slider/Slider.test.js +++ b/packages/mui-material/src/Slider/Slider.test.js @@ -40,7 +40,7 @@ describe('', () => { testDeepOverrides: { slotName: 'thumb', slotClassName: classes.thumb }, testVariantProps: { color: 'primary', orientation: 'vertical', size: 'small' }, testStateOverrides: { prop: 'color', value: 'secondary', styleKey: 'colorSecondary' }, - slots: { + components: { root: { expectedClassName: classes.root, }, diff --git a/packages/mui-material/src/StepLabel/StepLabel.test.js b/packages/mui-material/src/StepLabel/StepLabel.test.js index dcc23e5cc290e3..1eeed002792861 100644 --- a/packages/mui-material/src/StepLabel/StepLabel.test.js +++ b/packages/mui-material/src/StepLabel/StepLabel.test.js @@ -17,7 +17,7 @@ describe('', () => { render, refInstanceof: window.HTMLSpanElement, testVariantProps: { error: true }, - slots: { + components: { label: { expectedClassName: classes.label }, }, skip: ['componentProp', 'componentsProp', 'slotsProp'], diff --git a/packages/mui-material/src/Tooltip/Tooltip.test.js b/packages/mui-material/src/Tooltip/Tooltip.test.js index 9193eeb52c4733..d2766089bac1e6 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.test.js +++ b/packages/mui-material/src/Tooltip/Tooltip.test.js @@ -42,7 +42,7 @@ describe('', () => { refInstanceof: window.HTMLButtonElement, testRootOverrides: { slotName: 'popper', slotClassName: classes.popper }, testDeepOverrides: { slotName: 'tooltip', slotClassName: classes.tooltip }, - slots: { + components: { popper: { expectedClassName: classes.popper, testWithComponent: TestPopper, From 9474ae0da20177f770eac092c2dfa91969d6adb7 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:18:31 +0000 Subject: [PATCH 090/314] [docs] Iterating on recent Joy UI Component page updates (#35162) Co-authored-by: Olivier Tassinari --- docs/data/joy/components/alert/AlertColors.js | 25 ++---- .../data/joy/components/alert/AlertColors.tsx | 25 ++---- docs/data/joy/components/alert/alert.md | 61 ++++++--------- .../components/aspect-ratio/aspect-ratio.md | 77 ++++++------------- .../joy/components/avatar/AvatarVariants.js | 2 +- .../joy/components/avatar/AvatarVariants.tsx | 2 +- .../avatar/AvatarVariants.tsx.preview | 2 +- .../data/joy/components/avatar/BasicAvatar.js | 6 ++ .../components/avatar/BasicAvatar.tsx.preview | 1 + .../joy/components/avatar/BasicAvatars.js | 13 ++++ .../joy/components/avatar/BasicAvatars.tsx | 13 ++++ .../avatar/BasicAvatars.tsx.preview | 3 + docs/data/joy/components/avatar/avatar.md | 67 ++++++++-------- 13 files changed, 131 insertions(+), 166 deletions(-) create mode 100644 docs/data/joy/components/avatar/BasicAvatar.js create mode 100644 docs/data/joy/components/avatar/BasicAvatar.tsx.preview create mode 100644 docs/data/joy/components/avatar/BasicAvatars.js create mode 100644 docs/data/joy/components/avatar/BasicAvatars.tsx create mode 100644 docs/data/joy/components/avatar/BasicAvatars.tsx.preview diff --git a/docs/data/joy/components/alert/AlertColors.js b/docs/data/joy/components/alert/AlertColors.js index 42d37cc6db85a7..1425c169eab282 100644 --- a/docs/data/joy/components/alert/AlertColors.js +++ b/docs/data/joy/components/alert/AlertColors.js @@ -1,11 +1,12 @@ +import * as React from 'react'; import Alert from '@mui/joy/Alert'; +import Stack from '@mui/joy/Stack'; import Box from '@mui/joy/Box'; import Radio from '@mui/joy/Radio'; import RadioGroup from '@mui/joy/RadioGroup'; import Sheet from '@mui/joy/Sheet'; import Typography from '@mui/joy/Typography'; -import * as React from 'react'; export default function AlertColors() { const [variant, setVariant] = React.useState('solid'); @@ -14,16 +15,11 @@ export default function AlertColors() { sx={{ display: 'flex', alignItems: 'center', - gap: 3, + justifyContent: 'center', + width: '100%', }} > - + Primary @@ -42,15 +38,8 @@ export default function AlertColors() { Warning - - + + ('solid'); @@ -14,16 +15,11 @@ export default function AlertColors() { sx={{ display: 'flex', alignItems: 'center', - gap: 3, + justifyContent: 'center', + width: '100%', }} > - + Primary @@ -42,15 +38,8 @@ export default function AlertColors() { Warning - - + + ; -} ``` -## Basics - The Alert component wraps around its content, and stretches to fill its enclosing container, as shown below: {{"demo": "AlertBasic.js"}} -## Anatomy - -The Alert component is composed of a single root `
      ` element with its `role` set to `alert`: - -```html - -``` - -### Overriding the root slot +## Customization -Use the `component` prop to override the root slot with a custom element. -For example, the following code snippet replaces the default `
      ` with a ``: +### Variants -```jsx -Alert content +The Alert component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `solid`, `soft` (default), `outlined`, and `plain`. -// renders as: - - Alert content - -``` +{{"demo": "AlertVariants.js"}} -## Customization +:::success +To learn how to add your own variants, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +Note that you lose the global variants when you add custom variants. +::: -### Variants +### Sizes -The Alert component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `soft` (default), `solid`, `outlined`, and `plain`. +The Alert component comes in three sizes: `sm`, `md` (default), and `lg`: -{{"demo": "AlertVariants.js"}} +{{"demo": "AlertSizes.js"}} ### Colors @@ -79,12 +58,6 @@ The demo below shows how the values for the `color` prop are affected by the glo {{"demo": "AlertColors.js"}} -### Sizes - -The Alert component comes with three sizes out of the box: `sm`, `md` (the default), and `lg`: - -{{"demo": "AlertSizes.js"}} - ### Decorators Use the `startDecorator` and `endDecorator` props to append actions and icons to either side of the Alert: @@ -111,3 +84,13 @@ Here are some factors to consider to ensure that your Alert is accessible: - Alerts that occur too frequently can [inhibit the usability](https://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-postponed.html) of your app. - Dynamically rendered alerts are announced by screen readers; alerts that are already present on the page when it loads are _not_ announced. - Color does not add meaning to the UI for users who require assistive technology. You must ensure that any information conveyed through color is also denoted in other ways, such as within the text of the alert itself, or with additional hidden text that's read by screen readers. + +## Anatomy + +The Alert component is composed of a single root `
      ` element with its `role` set to `alert`: + +```html + +``` diff --git a/docs/data/joy/components/aspect-ratio/aspect-ratio.md b/docs/data/joy/components/aspect-ratio/aspect-ratio.md index bb6b4d694d8dbc..f92f7056a29639 100644 --- a/docs/data/joy/components/aspect-ratio/aspect-ratio.md +++ b/docs/data/joy/components/aspect-ratio/aspect-ratio.md @@ -18,76 +18,31 @@ As of Q4 2022, compatibility is at 90%. Source: [Can I use…](https://caniuse.com/?search=aspect-ratio) ::: -## Usage - -After [installation](/joy-ui/getting-started/installation/), you can start building with this component using the following basic elements: +## Basics ```jsx import AspectRatio from '@mui/joy/AspectRatio'; - -export default function MyApp() { - return ; -} ``` -## Basics - The Aspect Ratio component wraps around the content that it resizes. The element to be resized must be the first direct child. The default ratio is `16/9`. {{"demo": "BasicRatio.js"}} -## Anatomy - -The Aspect Ratio component is composed of a root `
      ` with a content `
      ` nested inside; the child component is given a `data-first-child` attribute for styling purposes: - -```html -
      -
      - - This is how an Aspect Ratio component renders in the DOM. - -
      -
      -``` - -### Overriding the root slot - -Use the `component` prop to override the root slot with a custom element. -For example, the following code snippet replaces the default `
      ` with a `
      `: - -```jsx - -``` - -### Overriding interior slots - -Use the `components` prop to override any interior slots in addition to the root: - - - -:::warning -If the root element is customized with both the `component` and `components` props, then `component` will take precedence. -::: - -Use the `componentsProps` prop to pass custom props to internal slots. -The following code snippet applies a CSS class called `my-content` to the content slot: - - - -:::warning -Note that `componentsProps` slot names are written in lowercase (root) while `components` slot names are capitalized (Root). -::: - ## Customization ### Variants -The Aspect Ratio component supports the four [global variants](/joy-ui/main-features/global-variants/): `solid`, `soft` (default), `outlined`, and `plain`. +The Aspect Ratio component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `solid`, `soft` (default), `outlined`, and `plain`. {{"demo": "VariantsRatio.js"}} +:::success +To learn how to add your own variants, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +Note that you lose the global variants when you add custom variants. +::: + ### Ratio Use the `ratio` prop to change the aspect ratio, following the pattern `height/width`. @@ -120,13 +75,13 @@ This is useful when the Aspect Ratio component wraps dynamic-width content, as s {{"demo": "MinMaxRatio.js"}} -### Usage inside a flex row +## Usage inside a flex row When the Aspect Ratio component is a child of a flexbox `row` container, use `flex-basis` to set the ideal width of the content: {{"demo": "FlexRowRatio.js"}} -### Usage with Next.js Image component +## Usage with Next.js Image The Aspect Ratio component can be used with a [Next.js Image](https://nextjs.org/docs/basic-features/image-optimization) component as a child. The Image should always include the `layout="fill"` property—otherwise it requires `height` and `width` values, which would defeat the purpose of the Aspect Ratio component. @@ -161,3 +116,17 @@ In designs like this, make sure to assign a `minWidth` value to prevent the Aspe This is a simple illustration of how to use Aspect Ratio with list components: {{"demo": "ListStackRatio.js"}} + +## Anatomy + +The Aspect Ratio component is composed of a root `
      ` with a content `
      ` nested inside; the child component is given a `data-first-child` attribute for styling purposes: + +```html +
      +
      + + + +
      +
      +``` diff --git a/docs/data/joy/components/avatar/AvatarVariants.js b/docs/data/joy/components/avatar/AvatarVariants.js index 1bc80bcf652dc7..6193bafcb947c9 100644 --- a/docs/data/joy/components/avatar/AvatarVariants.js +++ b/docs/data/joy/components/avatar/AvatarVariants.js @@ -5,8 +5,8 @@ import Box from '@mui/joy/Box'; export default function AvatarVariants() { return ( - + diff --git a/docs/data/joy/components/avatar/AvatarVariants.tsx b/docs/data/joy/components/avatar/AvatarVariants.tsx index 1bc80bcf652dc7..6193bafcb947c9 100644 --- a/docs/data/joy/components/avatar/AvatarVariants.tsx +++ b/docs/data/joy/components/avatar/AvatarVariants.tsx @@ -5,8 +5,8 @@ import Box from '@mui/joy/Box'; export default function AvatarVariants() { return ( - + diff --git a/docs/data/joy/components/avatar/AvatarVariants.tsx.preview b/docs/data/joy/components/avatar/AvatarVariants.tsx.preview index fa5e66d9f5ae97..0570a58cf0989f 100644 --- a/docs/data/joy/components/avatar/AvatarVariants.tsx.preview +++ b/docs/data/joy/components/avatar/AvatarVariants.tsx.preview @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/docs/data/joy/components/avatar/BasicAvatar.js b/docs/data/joy/components/avatar/BasicAvatar.js new file mode 100644 index 00000000000000..e2ac407dfae025 --- /dev/null +++ b/docs/data/joy/components/avatar/BasicAvatar.js @@ -0,0 +1,6 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; + +export default function BasicAvatar() { + return ; +} diff --git a/docs/data/joy/components/avatar/BasicAvatar.tsx.preview b/docs/data/joy/components/avatar/BasicAvatar.tsx.preview new file mode 100644 index 00000000000000..23517495fdf8ab --- /dev/null +++ b/docs/data/joy/components/avatar/BasicAvatar.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/joy/components/avatar/BasicAvatars.js b/docs/data/joy/components/avatar/BasicAvatars.js new file mode 100644 index 00000000000000..92ce23634a12de --- /dev/null +++ b/docs/data/joy/components/avatar/BasicAvatars.js @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Box from '@mui/joy/Box'; + +export default function BasicAvatars() { + return ( + + + JG + + + ); +} diff --git a/docs/data/joy/components/avatar/BasicAvatars.tsx b/docs/data/joy/components/avatar/BasicAvatars.tsx new file mode 100644 index 00000000000000..92ce23634a12de --- /dev/null +++ b/docs/data/joy/components/avatar/BasicAvatars.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Box from '@mui/joy/Box'; + +export default function BasicAvatars() { + return ( + + + JG + + + ); +} diff --git a/docs/data/joy/components/avatar/BasicAvatars.tsx.preview b/docs/data/joy/components/avatar/BasicAvatars.tsx.preview new file mode 100644 index 00000000000000..39b1405e527ec3 --- /dev/null +++ b/docs/data/joy/components/avatar/BasicAvatars.tsx.preview @@ -0,0 +1,3 @@ + +JG + \ No newline at end of file diff --git a/docs/data/joy/components/avatar/avatar.md b/docs/data/joy/components/avatar/avatar.md index efb136b051115c..f482e46f564ade 100644 --- a/docs/data/joy/components/avatar/avatar.md +++ b/docs/data/joy/components/avatar/avatar.md @@ -16,34 +16,16 @@ The Avatar component can be used to display graphical information about a user i {{"component": "modules/components/ComponentLinkHeader.js", "design": false}} -## Component - -After [installation](/joy-ui/getting-started/installation/), you can start building with this component using the following basic elements: +## Basics ```jsx import Avatar from '@mui/joy/Avatar'; - -export default function MyApp() { - return ; -} ``` -## Basics - By default, the Avatar component displays a generic Person Icon. -Wrap it around a string to display plain text, or use the `src` prop to display an image. - -## Anatomy +You can replace this icon with a text string or an image. -The Avatar component is composed of a root `
      ` that may wrap around an ``, an ``, or a string: - -```html -
      - -
      -``` - -## Customization +{{"demo": "BasicAvatars.js"}} ### Text Avatar @@ -59,7 +41,7 @@ Make sure to to write a meaningful description for the `alt` prop. {{"demo": "ImageAvatars.js"}} -### Image fallbacks +#### Image fallbacks If an error occurs while loading the Avatar's image, it will fall back to the following alternatives (in this order): @@ -69,19 +51,22 @@ If an error occurs while loading the Avatar's image, it will fall back to the fo {{"demo": "FallbackAvatars.js"}} +## Customization + ### Variants -The Avatar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `soft` (default), `solid`, `outlined`, and `plain`. +The Avatar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `solid`, `soft` (default), `outlined`, and `plain`. {{"demo": "AvatarVariants.js"}} :::success -To learn how to add more variants to the component, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +To learn how to add your own variants, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +Note that you lose the global variants when you add custom variants. ::: ### Sizes -The Avatar component comes in three sizes: `sm`, `md` (the default), and `lg`: +The Avatar component comes in three sizes: `sm`, `md` (default), and `lg`: {{"demo": "AvatarSizes.js"}} @@ -89,20 +74,14 @@ The Avatar component comes in three sizes: `sm`, `md` (the default), and `lg`: To learn how to add custom sizes to the component, check out [Themed components—Extend sizes](/joy-ui/customization/themed-components/#extend-sizes). ::: -## Usage with the Badge - -Combine the Avatar component with the [Badge](/joy-ui/react-badge/) to visually communicate more complex information about a user's status: - -{{"demo": "BadgeAvatars.js"}} - -## Usage with the Avatar Group - -Use the Avatar Group component to group multiple Avatars together. +## Usage with Avatar Group ```jsx import AvatarGroup from '@mui/joy/AvatarGroup'; ``` +Use the Avatar Group component to group multiple Avatars together. + {{"demo": "GroupedAvatars.js"}} ### Quantity within a group @@ -141,9 +120,29 @@ This approach is preferable because it preserves the overlapping offset between {{"demo": "VerticalAvatarGroup.js"}} +## Usage with Badge + +```jsx +import Badge from '@mui/joy/Badge'; +``` + +Combine the Avatar component with the [Badge](/joy-ui/react-badge/) to visually communicate more complex information about a user's status: + +{{"demo": "BadgeAvatars.js"}} + ## CSS variable playground Play around with the CSS variables available to the Avatar component to see how the design changes. You can use these to customize the component with both the `sx` prop and the theme. {{"demo": "AvatarGroupVariables.js", "hideToolbar": true }} + +## Anatomy + +The Avatar component is composed of a root `
      ` that may wrap around an ``, an ``, or a string: + +```html +
      + +
      +``` From 8bd75c8414334cf377d499f4a6e07e2cdd99d4e3 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sat, 19 Nov 2022 01:26:36 +0100 Subject: [PATCH 091/314] [website] Disable SEO for performance pages (#35173) --- docs/public/_headers | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/public/_headers b/docs/public/_headers index d4afc703021d50..34800ae1542e49 100644 --- a/docs/public/_headers +++ b/docs/public/_headers @@ -7,6 +7,9 @@ /favicon.ico Content-Type: image/x-icon +/performance/* + X-Robots-Tag: noindex + /* Strict-Transport-Security: max-age=31536000; includeSubDomains; preload # Block usage in iframes. From ec16c07219707530ce047756c5ef4cbadc7bca7b Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 20 Nov 2022 12:17:52 +0100 Subject: [PATCH 092/314] [docs] Fix typo in legacy date picker migration guide --- .../data/material/guides/pickers-migration/pickers-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/material/guides/pickers-migration/pickers-migration.md b/docs/data/material/guides/pickers-migration/pickers-migration.md index ea8509d2061050..93f2928fe7e0d6 100644 --- a/docs/data/material/guides/pickers-migration/pickers-migration.md +++ b/docs/data/material/guides/pickers-migration/pickers-migration.md @@ -1,4 +1,4 @@ -# Migration from @material-ui-pickers +# Migration from @material-ui/pickers

      @material-ui/pickers was moved to the @mui/lab.

      From 0200647d97c78dae53890fd813220ae9dfb25e29 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Nov 2022 15:49:37 +0100 Subject: [PATCH 093/314] Bump github/codeql-action digest to d00e8c0 (#35203) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e477d02eda1aa2..d8d30db10b860b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # v2.1.13 + uses: github/codeql-action/init@d00e8c09a38ef8c1ca1091fc55ef490776d2de73 # v2.1.13 with: languages: typescript config-file: ./.github/codeql/codeql-config.yml @@ -30,4 +30,4 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # v2.1.13 + uses: github/codeql-action/analyze@d00e8c09a38ef8c1ca1091fc55ef490776d2de73 # v2.1.13 From 838d1b5228b28324e505ea8d4f034194dda08b25 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Nov 2022 15:49:47 +0100 Subject: [PATCH 094/314] Bump github/codeql-action action to v1.1.33 (#35209) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 15a52171bae294..e42ad015259f6a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -43,6 +43,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@9e288b03632e540432812c08ffaef313da7fb1d9 # v1.1.31 + uses: github/codeql-action/upload-sarif@73113785b9e3aa4b2c9c2e1c91463606e882665e # v1.1.33 with: sarif_file: results.sarif From 59e1bc13ff682e718fd2f0cf2627323c6821924d Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 20 Nov 2022 15:51:04 +0100 Subject: [PATCH 095/314] [core] Group renovate GitHub Action dependency updates Apply https://github.com/mui/mui-toolpad/pull/1341 --- renovate.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/renovate.json b/renovate.json index ec0cd29e17c9e0..242bee9bcf2006 100644 --- a/renovate.json +++ b/renovate.json @@ -107,6 +107,10 @@ { "matchDepTypes": ["action"], "pinDigests": true + }, + { + "groupName": "GitHub Actions", + "matchManagers": ["github-actions"] } ], "postUpdateOptions": ["yarnDedupeHighest"], From 8817230087bc5f71616fac4af983216ca37da88a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 20 Nov 2022 16:46:45 +0100 Subject: [PATCH 096/314] the pages have no Date: Mon, 21 Nov 2022 00:01:07 +0800 Subject: [PATCH 097/314] [docs] Fix typos in section titles (#35025) --- .../customization/how-to-customize/how-to-customize-pt.md | 2 +- .../customization/how-to-customize/how-to-customize-zh.md | 2 +- .../material/customization/how-to-customize/how-to-customize.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/material/customization/how-to-customize/how-to-customize-pt.md b/docs/data/material/customization/how-to-customize/how-to-customize-pt.md index 4c598f839efee6..1846978154e50f 100644 --- a/docs/data/material/customization/how-to-customize/how-to-customize-pt.md +++ b/docs/data/material/customization/how-to-customize/how-to-customize-pt.md @@ -11,7 +11,7 @@ Material UI provides several different ways to customize a component's styles. Y 1. [One-off customization](#1-one-off-customization) 1. [Reusable component](#2-reusable-component) -1. [Global theme variation](#3-global-theme-overrides) +1. [Global theme overrides](#3-global-theme-overrides) 1. [Global CSS override](#4-global-css-override) ## 1. One-off customization diff --git a/docs/data/material/customization/how-to-customize/how-to-customize-zh.md b/docs/data/material/customization/how-to-customize/how-to-customize-zh.md index faacfd5dbc62d9..6e492c336b8789 100644 --- a/docs/data/material/customization/how-to-customize/how-to-customize-zh.md +++ b/docs/data/material/customization/how-to-customize/how-to-customize-zh.md @@ -11,7 +11,7 @@ Material UI provides several different ways to customize a component's styles. Y 1. [一次性定制](#1-one-off-customization) 1. [Reusable component](#2-reusable-component) -1. [Global theme variation](#3-global-theme-overrides) +1. [Global theme overrides](#3-global-theme-overrides) 1. [Global CSS override](#4-global-css-override) ## 1. 1. 1. 一次性定制 diff --git a/docs/data/material/customization/how-to-customize/how-to-customize.md b/docs/data/material/customization/how-to-customize/how-to-customize.md index a5256f32c341a5..4a7e152a27cb29 100644 --- a/docs/data/material/customization/how-to-customize/how-to-customize.md +++ b/docs/data/material/customization/how-to-customize/how-to-customize.md @@ -11,7 +11,7 @@ Material UI provides several different ways to customize a component's styles. Y 1. [One-off customization](#1-one-off-customization) 1. [Reusable component](#2-reusable-component) -1. [Global theme variation](#3-global-theme-overrides) +1. [Global theme overrides](#3-global-theme-overrides) 1. [Global CSS override](#4-global-css-override) ## 1. One-off customization From 556d3b0c556460ae76016dd1f14d54910766e3fc Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Mon, 21 Nov 2022 14:33:39 +0700 Subject: [PATCH 098/314] [website] Exclude experiment pages in production (#35180) --- docs/next.config.js | 6 +++++- docs/nextConfigDocsInfra.js | 17 ++++++++++++++++- docs/pages/experiments/index.js | 8 +++----- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/next.config.js b/docs/next.config.js index 116fe8d5211d1f..de65ad6f342b8b 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -170,7 +170,11 @@ module.exports = withDocsInfra({ const prefix = userLanguage === 'en' ? '' : `/${userLanguage}`; pages2.forEach((page) => { - if (page.pathname.startsWith('/experiments') && process.env.DEPLOY_ENV !== 'production') { + // The experiments pages are only meant for experiments, they shouldn't leak to production. + if ( + (page.pathname.startsWith('/experiments/') || page.pathname === '/experiments') && + process.env.DEPLOY_ENV === 'production' + ) { return; } // The blog is not translated diff --git a/docs/nextConfigDocsInfra.js b/docs/nextConfigDocsInfra.js index 6b8271940508ff..296de19a2a0a5f 100644 --- a/docs/nextConfigDocsInfra.js +++ b/docs/nextConfigDocsInfra.js @@ -1,3 +1,14 @@ +/** + * See the docs of the Netlify environment variables: + * https://docs.netlify.com/configure-builds/environment-variables/#build-metadata. + * + * A few comments: + * - process.env.CONTEXT === 'production' means that the branch in Netlify was configured as production. + * For example, the `master` branch of the Core team is considered a `production` build on Netlify based + * on https://app.netlify.com/sites/material-ui/settings/deploys#branches. + * - Each team has different site https://app.netlify.com/teams/mui/sites. + * The following logic must be compatible with all of them. + */ let DEPLOY_ENV = 'development'; // Same as process.env.PULL_REQUEST_ID @@ -9,12 +20,16 @@ if (process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-depl DEPLOY_ENV = 'production'; } +// The 'master' and 'next' branches are NEVER a production environment. We use these branches for staging. if ( - process.env.CONTEXT === 'branch-deploy' && + (process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-deploy') && (process.env.HEAD === 'master' || process.env.HEAD === 'next') ) { DEPLOY_ENV = 'staging'; } +/** + * ==================================================================================== + */ process.env.DEPLOY_ENV = DEPLOY_ENV; diff --git a/docs/pages/experiments/index.js b/docs/pages/experiments/index.js index bac1aa877e6c4d..b16f134d1c0d74 100644 --- a/docs/pages/experiments/index.js +++ b/docs/pages/experiments/index.js @@ -56,13 +56,11 @@ export default function Experiments({ experiments }) {
        - All the files under /experiments are committed to git. + The files under /experiments/* are committed to git. - URLs start with /experiments/* are deployed only on the pull request. - - - /experiments/* are not included in docsearch indexing. + These URLs (start with /experiments/*) are not accessible in + production.
      From d37b9baf5c7596e9637cf893bb872dd53785a9da Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Mon, 21 Nov 2022 15:01:14 +0700 Subject: [PATCH 099/314] [website] Fix theme mode toggle state (#35216) --- docs/pages/_document.js | 2 +- docs/src/BrandingCssVarsProvider.tsx | 2 +- docs/src/components/header/ThemeModeToggle.tsx | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/pages/_document.js b/docs/pages/_document.js index 4066cfa16bfc1c..718c9a2e882fec 100644 --- a/docs/pages/_document.js +++ b/docs/pages/_document.js @@ -145,7 +145,7 @@ export default class MyDocument extends Document { /> - {getMuiInitColorSchemeScript()} + {getMuiInitColorSchemeScript({ defaultMode: 'system' })}