diff --git a/docs/.env b/docs/.env index e79e6f46e8e1a3..f0fe34e156684a 100644 --- a/docs/.env +++ b/docs/.env @@ -1,2 +1,6 @@ +BUILD_ONLY_ENGLISH_LOCALE=true FEEDBACK_URL=https://hgvi836wi8.execute-api.us-east-1.amazonaws.com -NEXT_PUBLIC_MUI_CHAT_API_BASE_URL=https://chat-backend.mui.com \ No newline at end of file +GITHUB_TEMPLATE_DOCS_FEEDBACK=4.docs-feedback.yml +NEXT_PUBLIC_MUI_CHAT_API_BASE_URL=https://chat-backend.mui.com +SOURCE_CODE_REPO=https://github.com/mui/material-ui +SOURCE_GITHUB_BRANCH=master diff --git a/docs/README.md b/docs/README.md index e564a475ff68fe..d9793311de3d81 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,6 @@ Package managers other than pnpm (like npm or Yarn) are not supported and will n [You can follow this guide](https://github.com/mui/material-ui/blob/HEAD/CONTRIBUTING.md) on how to get started contributing to MUI. -## How do I help to improve the translations? +## Translations -Please visit https://crowdin.com/project/material-ui-docs where you will be able to select a language and edit the translations. -Please don't submit pull requests directly. +Are currently disabled https://github.com/mui/material-ui/issues/33445 diff --git a/docs/app/appWrapper.tsx b/docs/app/appWrapper.tsx new file mode 100644 index 00000000000000..f4c91c9b07fdf8 --- /dev/null +++ b/docs/app/appWrapper.tsx @@ -0,0 +1,329 @@ +'use client'; +import { CodeCopyProvider } from '@mui/docs/CodeCopy'; +import { DocsProvider } from '@mui/docs/DocsProvider'; +import { useRouter } from '@mui/docs/routing'; +import joyPkgJson from '@mui/joy/package.json'; +import materialPkgJson from '@mui/material/package.json'; +import systemPkgJson from '@mui/system/package.json'; +import { LicenseInfo } from '@mui/x-license'; +import docsInfraPages from 'docs/data/docs-infra/pages'; +import generalDocsPages from 'docs/data/docs/pages'; +import joyPages from 'docs/data/joy/pages'; +import materialPages from 'docs/data/material/pages'; +import systemPages from 'docs/data/system/pages'; +import SvgMuiLogomark, { + muiSvgLogoString, + muiSvgWordmarkString, +} from 'docs/src/icons/SvgMuiLogomark'; +import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics'; +import PageContext from 'docs/src/modules/components/PageContext'; +import { ThemeProvider } from 'docs/src/modules/components/ThemeContext'; +import { CodeVariantProvider } from 'docs/src/modules/utils/codeVariant'; +import findActivePage from 'docs/src/modules/utils/findActivePage'; +import getProductInfoFromUrl from 'docs/src/modules/utils/getProductInfoFromUrl'; +import { pathnameToLanguage } from 'docs/src/modules/utils/helpers'; +import { defaultLanguage, getTranslations } from 'docs/src/modules/utils/i18n'; +import DocsStyledEngineProvider from 'docs/src/modules/utils/StyledEngineProviderApp'; +import { loadCSS } from 'fg-loadcss'; +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import * as config from '../config'; + +interface Props { + children: React.ReactNode; + userLanguage: string; +} + +// Remove the license warning from demonstration purposes +LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_LICENSE!); + +let reloadInterval: number; + +// Avoid infinite loop when "Upload on reload" is set in the Chrome sw dev tools. +function lazyReload() { + window.clearInterval(reloadInterval); + reloadInterval = window.setInterval(() => { + if (document.hasFocus()) { + window.location.reload(); + } + }, 100); +} + +// Inspired by +// https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users +function forcePageReload(registration: ServiceWorkerRegistration) { + if (!navigator.serviceWorker.controller) { + // The window client isn't currently controlled so it's a new service + // worker that will activate immediately. + return; + } + + if (registration.waiting) { + // SW is waiting to activate. Can occur if multiple clients open and + // one of the clients is refreshed. + registration.waiting.postMessage('skipWaiting'); + return; + } + + function listenInstalledStateChange() { + registration.installing!.addEventListener('statechange', (event) => { + const target = event.target as unknown as { state: string }; + + if (target.state === 'installed' && registration.waiting) { + // A new service worker is available, inform the user + registration.waiting.postMessage('skipWaiting'); + } else if (target.state === 'activated') { + // Force the control of the page by the activated service worker. + lazyReload(); + } + }); + } + + if (registration.installing) { + listenInstalledStateChange(); + return; + } + + // We are currently controlled so a new SW may be found... + // Add a listener in case a new SW is found, + registration.addEventListener('updatefound', listenInstalledStateChange); +} + +async function registerServiceWorker() { + if ( + 'serviceWorker' in navigator && + process.env.NODE_ENV === 'production' && + window.location.host.includes('mui.com') + ) { + // register() automatically attempts to refresh the sw.js. + const registration = await navigator.serviceWorker.register('/sw.js'); + forcePageReload(registration); + } +} + +let dependenciesLoaded = false; + +function loadDependencies() { + if (dependenciesLoaded) { + return; + } + + dependenciesLoaded = true; + + loadCSS( + 'https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Two+Tone', + document.querySelector('#material-icon-font') as HTMLElement, + ); +} + +/** + * Preconnect allows the browser to setup early connections before an HTTP request + is actually sent to the server. + This includes DNS lookups, TLS negotiations, TCP handshakes. + */ +function preconnectResources() { + ReactDOM.preconnect('https://fonts.gstatic.com', { crossOrigin: 'anonymous' }); + ReactDOM.preconnect('https://fonts.googleapis.com'); +} + +if (typeof window !== 'undefined' && process.env.NODE_ENV === 'production') { + // eslint-disable-next-line no-console + console.log( + `%c + +███╗ ███╗ ██╗ ██╗ ██████╗ +████╗ ████║ ██║ ██║ ██╔═╝ +██╔████╔██║ ██║ ██║ ██║ +██║╚██╔╝██║ ██║ ██║ ██║ +██║ ╚═╝ ██║ ╚██████╔╝ ██████╗ +╚═╝ ╚═╝ ╚═════╝ ╚═════╝ + +Tip: you can access the documentation \`theme\` object directly in the console. +`, + 'font-family:monospace;color:#1976d2;font-size:12px;', + ); +} + +export default function AppWrapper(props: Props) { + const { children, userLanguage } = props; + + const router = useRouter(); + const translations = getTranslations(); + + // TODO move productId & productCategoryId resolution to page layout. + // We should use the productId field from the markdown and fallback to getProductInfoFromUrl() + // if not present + const { productId, productCategoryId } = getProductInfoFromUrl(router.pathname); + + React.useEffect(() => { + loadDependencies(); + registerServiceWorker(); + preconnectResources(); + + // Remove the server-side injected CSS. + const jssStyles = document.querySelector('#jss-server-side'); + if (jssStyles) { + jssStyles.parentElement!.removeChild(jssStyles); + } + }, []); + + const productIdentifier = React.useMemo(() => { + const languagePrefix = userLanguage === defaultLanguage ? '' : `/${userLanguage}`; + + if (productId === 'material-ui') { + return { + metadata: '', + name: 'Material UI', + logo: SvgMuiLogomark, + logoSvg: muiSvgLogoString, + wordmarkSvg: muiSvgWordmarkString, + versions: [ + { text: `v${materialPkgJson.version}`, current: true }, + { + text: 'v6', + href: `https://v6.mui.com${languagePrefix}/material-ui/getting-started/`, + }, + { + text: 'v5', + href: `https://v5.mui.com${languagePrefix}/getting-started/installation/`, + }, + { + text: 'v4', + href: `https://v4.mui.com${languagePrefix}/getting-started/installation/`, + }, + { + text: 'View all versions', + href: `https://mui.com${languagePrefix}/versions/`, + }, + ], + }; + } + + if (productId === 'joy-ui') { + return { + metadata: '', + name: 'Joy UI', + logo: SvgMuiLogomark, + logoSvg: muiSvgLogoString, + wordmarkSvg: muiSvgWordmarkString, + versions: [{ text: `v${joyPkgJson.version}`, current: true }], + }; + } + + if (productId === 'system') { + return { + metadata: '', + name: 'MUI System', + logo: SvgMuiLogomark, + logoSvg: muiSvgLogoString, + wordmarkSvg: muiSvgWordmarkString, + versions: [ + { text: `v${systemPkgJson.version}`, current: true }, + { text: 'v6', href: `https://v6.mui.com${languagePrefix}/system/getting-started/` }, + { text: 'v5', href: `https://v5.mui.com${languagePrefix}/system/getting-started/` }, + { text: 'v4', href: `https://v4.mui.com${languagePrefix}/system/basics/` }, + { + text: 'View all versions', + href: `https://mui.com${languagePrefix}/versions/`, + }, + ], + }; + } + + if (productId === 'docs-infra') { + return { + metadata: '', + name: 'Docs-infra', + logo: SvgMuiLogomark, + logoSvg: muiSvgLogoString, + wordmarkSvg: muiSvgWordmarkString, + versions: [ + { + text: 'v0.0.0', + href: `https://mui.com${languagePrefix}/versions/`, + }, + ], + }; + } + + if (productId === 'docs') { + return { + metadata: '', + name: 'Home docs', + logo: SvgMuiLogomark, + logoSvg: muiSvgLogoString, + wordmarkSvg: muiSvgWordmarkString, + versions: [ + { + text: 'v0.0.0', + href: `https://mui.com${languagePrefix}/versions/`, + }, + ], + }; + } + + return null; + }, [userLanguage, productId]); + + const pageContextValue = React.useMemo(() => { + let pages = generalDocsPages; + if (productId === 'material-ui') { + pages = materialPages; + } else if (productId === 'joy-ui') { + pages = joyPages; + } else if (productId === 'system') { + pages = systemPages; + } else if (productId === 'docs-infra') { + pages = docsInfraPages; + } + + const { activePage, activePageParents } = findActivePage([...pages], router.pathname); + + return { + activePage, + activePageParents, + pages: [...pages], + productIdentifier: productIdentifier!, + productId, + productCategoryId, + }; + }, [productId, productCategoryId, productIdentifier, router.pathname]); + + let fonts: string[] = [ + 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,400&display=swap', + ]; + if (pathnameToLanguage(router.pathname).canonicalAs.match(/onepirate/)) { + fonts = [ + 'https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@700&family=Work+Sans:wght@300;400&display=swap', + ]; + } + + return ( + + {fonts.map((font) => ( + + ))} + + + + + + + + + {children} + + + + + + + + + ); +} diff --git a/docs/app/custom404.tsx b/docs/app/custom404.tsx new file mode 100644 index 00000000000000..281b7df0aaf647 --- /dev/null +++ b/docs/app/custom404.tsx @@ -0,0 +1,21 @@ +'use client'; +import Divider from '@mui/material/Divider'; +import BrandingCssVarsProvider from 'docs/src/BrandingCssVarsProvider'; +import AppHeaderBanner from 'docs/src/components/banner/AppHeaderBanner'; +import NotFoundHero from 'docs/src/components/NotFoundHero'; +import AppFooter from 'docs/src/layouts/AppFooter'; +import AppHeader from 'docs/src/layouts/AppHeader'; + +export default function Custom404() { + return ( + + + +
+ + +
+ +
+ ); +} diff --git a/docs/app/global.css b/docs/app/global.css new file mode 100644 index 00000000000000..74d496b6e3b1c7 --- /dev/null +++ b/docs/app/global.css @@ -0,0 +1,133 @@ +@import 'tailwindcss/theme.css' layer(theme); +@import 'tailwindcss/utilities.css' layer(utilities); +@config '../tailwind.config.mjs'; + +/* + Loads General Sans: Regular (400), Medium (500), SemiBold (600), Bold (700) + Typeface documentation: https://www.fontshare.com/fonts/general-sans + use https://cssminifier.com/ to minify +*/ +@font-face { + font-family: 'General Sans'; + src: + url(/static/fonts/GeneralSans-Regular.woff2) format('woff2'), + url(/static/fonts/GeneralSans-Regular.ttf) format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'General Sans'; + src: + url(/static/fonts/GeneralSans-Medium.woff2) format('woff2'), + url(/static/fonts/GeneralSans-Medium.ttf) format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'General Sans'; + src: + url(/static/fonts/GeneralSans-SemiBold.woff2) format('woff2'), + url(/static/fonts/GeneralSans-SemiBold.ttf) format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'General Sans'; + src: + url(/static/fonts/GeneralSans-Bold.woff2) format('woff2'), + url(/static/fonts/GeneralSans-Bold.ttf) format('truetype'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +/* + Loads IBM Plex Sans: 400,500,700 & IBM Plex Mono: 400, 600 + use https://cssminifier.com/ to minify +*/ +@font-face { + font-family: 'IBM Plex Sans'; + src: + url(/static/fonts/IBMPlexSans-Regular.woff2) format('woff2'), + url(/static/fonts/IBMPlexSans-Regular.woff) format('woff'), + url(/static/fonts/IBMPlexSans-Regular.ttf) format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'IBM Plex Sans'; + src: + url(/static/fonts/IBMPlexSans-Medium.woff2) format('woff2'), + url(/static/fonts/IBMPlexSans-Medium.woff) format('woff'), + url(/static/fonts/IBMPlexSans-Medium.ttf) format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'IBM Plex Sans'; + src: + url(/static/fonts/IBMPlexSans-SemiBold.woff2) format('woff2'), + url(/static/fonts/IBMPlexSans-SemiBold.woff) format('woff'), + url(/static/fonts/IBMPlexSans-SemiBold.ttf) format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'IBM Plex Sans'; + src: + url(/static/fonts/IBMPlexSans-Bold.woff2) format('woff2'), + url(/static/fonts/IBMPlexSans-Bold.woff) format('woff'), + url(/static/fonts/IBMPlexSans-Bold.ttf) format('truetype'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +/* First SSR paint */ +.only-light-mode { + display: 'block'; +} +.only-dark-mode { + display: 'none'; +} + +/* Post SSR Hydration */ +.mode-dark .only-light-mode { + display: 'none'; +} +.mode-dark .only-dark-mode { + display: 'block'; +} + +/* TODO migrate to .only-dark-mode to .only-dark-mode-v2 */ +[data-mui-color-scheme='light'] .only-dark-mode-v2 { + display: 'none'; +} +[data-mui-color-scheme='dark'] .only-light-mode-v2 { + display: 'none'; +} + +.plan-pro, +.plan-premium { + display: 'inline-block'; + height: '0.9em'; + width: '1em'; + vertical-align: 'middle'; + margin-left: '0.3em'; + margin-bottom: '0.08em'; + background-size: 'contain'; + background-repeat: 'no-repeat'; + flex-shrink: 0; +} +.plan-pro { + background-image: 'url(/static/x/pro.svg)'; +} +.plan-premium { + background-image: 'url(/static/x/premium.svg)'; +} diff --git a/docs/pages/index.tsx b/docs/app/home.tsx similarity index 59% rename from docs/pages/index.tsx rename to docs/app/home.tsx index 0bb1798a14d507..32342f7ea41537 100644 --- a/docs/pages/index.tsx +++ b/docs/app/home.tsx @@ -1,50 +1,46 @@ -import * as React from 'react'; -import NoSsr from '@mui/material/NoSsr'; +'use client'; import Divider from '@mui/material/Divider'; -import Head from 'docs/src/modules/components/Head'; -import AppHeader from 'docs/src/layouts/AppHeader'; +import NoSsr from '@mui/material/NoSsr'; +import BrandingCssVarsProvider from 'docs/src/BrandingCssVarsProvider'; +import AppHeaderBanner from 'docs/src/components/banner/AppHeaderBanner'; +import DesignSystemComponents from 'docs/src/components/home/DesignSystemComponents'; import Hero from 'docs/src/components/home/Hero'; -import References, { CORE_CUSTOMERS } from 'docs/src/components/home/References'; +import HeroEnd from 'docs/src/components/home/HeroEnd'; +import NewsletterToast from 'docs/src/components/home/NewsletterToast'; import ProductSuite from 'docs/src/components/home/ProductSuite'; -import ValueProposition from 'docs/src/components/home/ValueProposition'; -import DesignSystemComponents from 'docs/src/components/home/DesignSystemComponents'; -import Testimonials from 'docs/src/components/home/Testimonials'; +import References, { CORE_CUSTOMERS } from 'docs/src/components/home/References'; import Sponsors from 'docs/src/components/home/Sponsors'; -import HeroEnd from 'docs/src/components/home/HeroEnd'; +import Testimonials from 'docs/src/components/home/Testimonials'; +import ValueProposition from 'docs/src/components/home/ValueProposition'; import AppFooter from 'docs/src/layouts/AppFooter'; -import BrandingCssVarsProvider from 'docs/src/BrandingCssVarsProvider'; -import NewsletterToast from 'docs/src/components/home/NewsletterToast'; -import AppHeaderBanner from 'docs/src/components/banner/AppHeaderBanner'; +import AppHeader from 'docs/src/layouts/AppHeader'; +import { Suspense } from 'react'; export default function Home() { return ( - -