From 5ae2408badf181c67b2235a089df911c21084c6a Mon Sep 17 00:00:00 2001 From: noraleonte Date: Mon, 23 Feb 2026 18:40:44 +0200 Subject: [PATCH 01/11] wip --- docs/pages/material-ui/react-table.js | 2 +- docs/src/modules/components/AppLayoutDocs.js | 32 +- .../modules/components/AppTableOfContents.js | 313 +++++++++++++----- docs/src/modules/components/MarkdownDocs.js | 3 + docs/src/modules/components/MarkdownDocsV2.js | 3 + 5 files changed, 262 insertions(+), 91 deletions(-) diff --git a/docs/pages/material-ui/react-table.js b/docs/pages/material-ui/react-table.js index 478f65148f9593..1362b654f398c3 100644 --- a/docs/pages/material-ui/react-table.js +++ b/docs/pages/material-ui/react-table.js @@ -3,7 +3,7 @@ import AppFrame from 'docs/src/modules/components/AppFrame'; import * as pageProps from 'docs/data/material/components/table/table.md?muiMarkdown'; export default function Page() { - return ; + return ; } Page.getLayout = (page) => { diff --git a/docs/src/modules/components/AppLayoutDocs.js b/docs/src/modules/components/AppLayoutDocs.js index 29a79b06248c69..75af61371a9ce5 100644 --- a/docs/src/modules/components/AppLayoutDocs.js +++ b/docs/src/modules/components/AppLayoutDocs.js @@ -14,14 +14,12 @@ import { import Head from 'docs/src/modules/components/Head'; import AppFrame from 'docs/src/modules/components/AppFrame'; import AppContainer from 'docs/src/modules/components/AppContainer'; -import AppTableOfContents from 'docs/src/modules/components/AppTableOfContents'; +import AppTableOfContents, { TOC_WIDTH } from 'docs/src/modules/components/AppTableOfContents'; import AppLayoutDocsFooter from 'docs/src/modules/components/AppLayoutDocsFooter'; import BackToTop from 'docs/src/modules/components/BackToTop'; import getProductInfoFromUrl from 'docs/src/modules/utils/getProductInfoFromUrl'; import { convertProductIdToName } from 'docs/src/modules/components/AppSearch'; -const TOC_WIDTH = 242; - const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'disableToc', })(({ theme }) => ({ @@ -35,7 +33,7 @@ const Main = styled('main', { { props: ({ disableToc }) => disableToc, style: { - [theme.breakpoints.up('md')]: { + [theme.breakpoints.up('xl')]: { marginRight: TOC_WIDTH / 2, }, }, @@ -43,7 +41,7 @@ const Main = styled('main', { { props: ({ disableToc }) => !disableToc, style: { - [theme.breakpoints.up('md')]: { + [theme.breakpoints.up('xl')]: { gridTemplateColumns: `1fr ${TOC_WIDTH}px`, }, }, @@ -52,7 +50,7 @@ const Main = styled('main', { })); const StyledAppContainer = styled(AppContainer, { - shouldForwardProp: (prop) => prop !== 'disableAd' && prop !== 'hasTabs' && prop !== 'disableToc', + shouldForwardProp: (prop) => prop !== 'disableAd' && prop !== 'hasTabs' && prop !== 'disableToc' && prop !== 'container', })(({ theme }) => { return { position: 'relative', @@ -65,14 +63,14 @@ const StyledAppContainer = styled(AppContainer, { }, variants: [ { - props: ({ disableToc }) => disableToc, + props: ({ disableToc, container }) => disableToc && container === 'narrow', style: { // 105ch ≈ 930px maxWidth: `calc(105ch + ${TOC_WIDTH / 2}px)`, }, }, { - props: ({ disableToc }) => !disableToc, + props: ({ disableToc, container }) => !disableToc && container === 'narrow', style: { // We're mostly hosting text content so max-width by px does not make sense considering font-size is system-adjustable. fontFamily: 'Arial', @@ -80,6 +78,15 @@ const StyledAppContainer = styled(AppContainer, { maxWidth: '105ch', }, }, + { + props: ({ disableToc, container }) => !disableToc && container === 'wide', + style: { + maxWidth: theme.breakpoints.values.xl, + '& p, & li, & h1, & h2, & h3, & h4, & h5, & h6': { + maxWidth: '105ch', + }, + }, + }, { props: ({ disableAd, hasTabs }) => !disableAd && hasTabs, style: { @@ -127,6 +134,7 @@ export default function AppLayoutDocs(props) { // improves the UX. It's faster to transition, and you don't lose UI states, like scroll. disableLayout = false, disableToc = false, + container = 'narrow', hasTabs = false, location, title, @@ -166,7 +174,12 @@ export default function AppLayoutDocs(props) { Render the TOCs first to avoid layout shift when the HTML is streamed. See https://jakearchibald.com/2014/dont-use-flexbox-for-page-layout/ for more details. */} - + {children} @@ -185,6 +198,7 @@ AppLayoutDocs.propTypes = { title: PropTypes.string, }), children: PropTypes.node.isRequired, + container: PropTypes.oneOf(['narrow', 'wide']), description: PropTypes.string.isRequired, disableAd: PropTypes.bool.isRequired, disableLayout: PropTypes.bool, diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js index e9ae60903a3df0..12bd3ff86e98d7 100644 --- a/docs/src/modules/components/AppTableOfContents.js +++ b/docs/src/modules/components/AppTableOfContents.js @@ -11,6 +11,16 @@ import { samePageLinkNavigation } from 'docs/src/modules/components/MarkdownLink import TableOfContentsBanner from 'docs/src/components/banner/TableOfContentsBanner'; import featureToggle from 'docs/src/featureToggle'; import DiamondSponsors from 'docs/src/modules/components/DiamondSponsors'; +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; +import ClickAwayListener from '@mui/material/ClickAwayListener'; +import Tooltip from '@mui/material/Tooltip'; +import Fab from '@mui/material/Fab'; +import Box from '@mui/material/Box'; +import Popper from '@mui/material/Popper'; +import Paper from '@mui/material/Paper'; +import Fade from '@mui/material/Fade'; + +export const TOC_WIDTH = 242; const Nav = styled('nav')(({ theme }) => ({ top: 'var(--MuiDocs-header-height)', @@ -24,7 +34,7 @@ const Nav = styled('nav')(({ theme }) => ({ paddingRight: theme.spacing(4), // We can't use `padding` as @mui/stylis-plugin-rtl doesn't swap it display: 'none', scrollbarWidth: 'thin', - [theme.breakpoints.up('md')]: { + [theme.breakpoints.up('xl')]: { display: 'block', }, })); @@ -168,14 +178,124 @@ function shouldShowJobAd() { const showJobAd = featureToggle.enable_job_banner && shouldShowJobAd(); +function TableOfContents({ toc, itemLink, onLinkClick }) { + const t = useTranslate(); + + return ( + + + {showJobAd && ( + ({ + mb: 2, + p: 1, + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + backgroundColor: alpha(theme.palette.grey[50], 0.4), + border: '1px solid', + borderColor: (theme.vars || theme).palette.grey[200], + borderRadius: 1, + transitionProperty: 'all', + transitionTiming: 'cubic-bezier(0.4, 0, 0.2, 1)', + transitionDuration: '150ms', + '&:hover, &:focus-visible': { + borderColor: (theme.vars || theme).palette.primary[200], + }, + }), + (theme) => + theme.applyDarkStyles({ + backgroundColor: alpha(theme.palette.primary[900], 0.2), + borderColor: (theme.vars || theme).palette.primaryDark[700], + '&:hover, &:focus-visible': { + borderColor: (theme.vars || theme).palette.primaryDark[500], + }, + }), + ]} + > + + {'🚀 Join the MUI team!'} + + + {/* eslint-disable-next-line material-ui/no-hardcoded-labels */} + {"We're looking for React Engineers and other amazing roles-come find out more!"} + + + )} + + {toc.length > 0 ? ( + + {t('tableOfContents')} + + {toc.map((item) => ( +
  • + {itemLink(item, 1, onLinkClick)} + {item.children.length > 0 ? ( + + {item.children.map((subitem) => ( +
  • + {itemLink(subitem, 2, onLinkClick)} + {subitem.children?.length > 0 ? ( + + {subitem.children.map((nestedSubItem) => ( +
  • + {itemLink(nestedSubItem, 3, onLinkClick)} +
  • + ))} +
    + ) : null} + + ))} + + ) : null} + + ))} + +
    + ) : null} + + +
    + ); +} + +TableOfContents.propTypes = { + itemLink: PropTypes.func.isRequired, + onLinkClick: PropTypes.func, + toc: PropTypes.array.isRequired, +}; + export default function AppTableOfContents(props) { const { toc } = props; const t = useTranslate(); const items = React.useMemo(() => flatten(toc), [toc]); const [activeState, setActiveState] = React.useState(null); + const [popperAnchorEl, setPopperAnchorEl] = React.useState(null); const clickedRef = React.useRef(false); const unsetClickedRef = React.useRef(null); + + const handlePopperOpen = (event) => { + setPopperAnchorEl(event.currentTarget); + }; + + const handlePopperClose = () => { + setPopperAnchorEl(null); + }; + + const popperOpen = Boolean(popperAnchorEl); + const findActiveIndex = React.useCallback(() => { // Don't set the active index based on scroll if a link was just clicked if (clickedRef.current) { @@ -241,12 +361,35 @@ export default function AppTableOfContents(props) { [], ); - const itemLink = (item, level) => ( + const itemLink = (item, level, onLinkClick) => ( { + handleClick(item.hash)(event); + if (onLinkClick) { + onLinkClick(); + } + }} + active={activeState === item.hash} + level={level} + > + + + ); + + const mobileItemLink = (item, level, onLinkClick) => ( + { + handleClick(item.hash)(event); + if (onLinkClick) { + onLinkClick(); + } + }} active={activeState === item.hash} level={level} > @@ -255,88 +398,96 @@ export default function AppTableOfContents(props) { ); return ( - + + + + ); } diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js index 4f3094abd4f6a0..0b6d16bf623691 100644 --- a/docs/src/modules/components/MarkdownDocs.js +++ b/docs/src/modules/components/MarkdownDocs.js @@ -9,6 +9,7 @@ export default function MarkdownDocs(props) { const { disableAd = false, disableToc = false, + container, demos = {}, docs, demoComponents, @@ -27,6 +28,7 @@ export default function MarkdownDocs(props) { description={localizedDoc.description} disableAd={disableAd} disableToc={disableToc} + container={container} location={localizedDoc.location} title={localizedDoc.title} toc={localizedDoc.toc} @@ -52,6 +54,7 @@ export default function MarkdownDocs(props) { } MarkdownDocs.propTypes = { + container: PropTypes.oneOf(['narrow', 'wide']), demoComponents: PropTypes.object, demos: PropTypes.object, disableAd: PropTypes.bool, diff --git a/docs/src/modules/components/MarkdownDocsV2.js b/docs/src/modules/components/MarkdownDocsV2.js index 5951d2c2f902ff..05c92f9a1fcda2 100644 --- a/docs/src/modules/components/MarkdownDocsV2.js +++ b/docs/src/modules/components/MarkdownDocsV2.js @@ -43,6 +43,7 @@ export default function MarkdownDocsV2(props) { const { disableAd = false, disableToc = false, + container, demos = {}, docs, demoComponents, @@ -220,6 +221,7 @@ export default function MarkdownDocsV2(props) { description={localizedDoc.description} disableAd={disableAd} disableToc={disableToc} + container={container} location={localizedDoc.location} title={localizedDoc.title} toc={activeToc} @@ -274,6 +276,7 @@ export default function MarkdownDocsV2(props) { MarkdownDocsV2.propTypes = { componentsApiDescriptions: PropTypes.object, componentsApiPageContents: PropTypes.object, + container: PropTypes.oneOf(['narrow', 'wide']), demoComponents: PropTypes.object, demos: PropTypes.object, disableAd: PropTypes.bool, From 036f3ce84293da821428eeecfde41abafe5079d4 Mon Sep 17 00:00:00 2001 From: noraleonte Date: Mon, 23 Feb 2026 19:09:36 +0200 Subject: [PATCH 02/11] wip --- .../banner/TableOfContentsBanner.tsx | 3 -- docs/src/modules/components/AppLayoutDocs.js | 3 +- docs/src/modules/components/AppNavDrawer.tsx | 37 ++++++++++++++++--- .../modules/components/AppTableOfContents.js | 4 -- .../src/modules/components/DiamondSponsors.js | 2 +- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/docs/src/components/banner/TableOfContentsBanner.tsx b/docs/src/components/banner/TableOfContentsBanner.tsx index 30456bbb5d3f14..b6d16bf200a808 100644 --- a/docs/src/components/banner/TableOfContentsBanner.tsx +++ b/docs/src/components/banner/TableOfContentsBanner.tsx @@ -10,9 +10,6 @@ export default function TableOfContentsBanner() { target="_blank" sx={[ (theme) => ({ - mt: 2, - mx: 0.5, - mb: 2, p: 1, pl: '10px', display: 'flex', diff --git a/docs/src/modules/components/AppLayoutDocs.js b/docs/src/modules/components/AppLayoutDocs.js index 75af61371a9ce5..eb4cde09dcbba1 100644 --- a/docs/src/modules/components/AppLayoutDocs.js +++ b/docs/src/modules/components/AppLayoutDocs.js @@ -50,7 +50,8 @@ const Main = styled('main', { })); const StyledAppContainer = styled(AppContainer, { - shouldForwardProp: (prop) => prop !== 'disableAd' && prop !== 'hasTabs' && prop !== 'disableToc' && prop !== 'container', + shouldForwardProp: (prop) => + prop !== 'disableAd' && prop !== 'hasTabs' && prop !== 'disableToc' && prop !== 'container', })(({ theme }) => { return { position: 'relative', diff --git a/docs/src/modules/components/AppNavDrawer.tsx b/docs/src/modules/components/AppNavDrawer.tsx index 23721232c30b29..ad409625aede25 100644 --- a/docs/src/modules/components/AppNavDrawer.tsx +++ b/docs/src/modules/components/AppNavDrawer.tsx @@ -24,6 +24,8 @@ import { pageToTitleI18n } from 'docs/src/modules/utils/helpers'; import PageContext, { ProductVersion } from 'docs/src/modules/components/PageContext'; import { useTranslate } from '@mui/docs/i18n'; import MuiProductSelector from 'docs/src/modules/components/MuiProductSelector'; +import DiamondSponsors from 'docs/src/modules/components/DiamondSponsors'; +import TableOfContentsBanner from 'docs/src/components/banner/TableOfContentsBanner'; import { MuiPage } from 'docs/src/MuiPage'; // TODO: Collapse should expose an API to customize the duration based on the height. @@ -468,10 +470,10 @@ export default function AppNavDrawer(props: AppNavDrawerProps) { - - {navItems} - + + + {navItems} + + + + + + ); diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js index 12bd3ff86e98d7..3ae857a62c6fc4 100644 --- a/docs/src/modules/components/AppTableOfContents.js +++ b/docs/src/modules/components/AppTableOfContents.js @@ -8,9 +8,7 @@ import NoSsr from '@mui/material/NoSsr'; import { Link } from '@mui/docs/Link'; import { useTranslate } from '@mui/docs/i18n'; import { samePageLinkNavigation } from 'docs/src/modules/components/MarkdownLinks'; -import TableOfContentsBanner from 'docs/src/components/banner/TableOfContentsBanner'; import featureToggle from 'docs/src/featureToggle'; -import DiamondSponsors from 'docs/src/modules/components/DiamondSponsors'; import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; import ClickAwayListener from '@mui/material/ClickAwayListener'; import Tooltip from '@mui/material/Tooltip'; @@ -264,8 +262,6 @@ function TableOfContents({ toc, itemLink, onLinkClick }) { ) : null} - - ); } diff --git a/docs/src/modules/components/DiamondSponsors.js b/docs/src/modules/components/DiamondSponsors.js index e17b5383f3461c..fb32bbb9830399 100644 --- a/docs/src/modules/components/DiamondSponsors.js +++ b/docs/src/modules/components/DiamondSponsors.js @@ -42,7 +42,7 @@ export default function DiamondSponsors() { const t = useTranslate(); return ( - + Date: Thu, 5 Mar 2026 16:44:35 +0200 Subject: [PATCH 03/11] adjust props --- docs/pages/material-ui/react-table.js | 2 +- ...ntsBanner.tsx => SideNavigationBanner.tsx} | 2 +- docs/src/modules/components/AppLayoutDocs.js | 23 +++++++--- docs/src/modules/components/AppNavDrawer.tsx | 5 ++- .../modules/components/AppTableOfContents.js | 44 ++++++++++++------- .../src/modules/components/DiamondSponsors.js | 4 +- docs/src/modules/components/MarkdownDocsV2.js | 3 ++ 7 files changed, 57 insertions(+), 26 deletions(-) rename docs/src/components/banner/{TableOfContentsBanner.tsx => SideNavigationBanner.tsx} (97%) diff --git a/docs/pages/material-ui/react-table.js b/docs/pages/material-ui/react-table.js index 1362b654f398c3..5ef6e874501644 100644 --- a/docs/pages/material-ui/react-table.js +++ b/docs/pages/material-ui/react-table.js @@ -3,7 +3,7 @@ import AppFrame from 'docs/src/modules/components/AppFrame'; import * as pageProps from 'docs/data/material/components/table/table.md?muiMarkdown'; export default function Page() { - return ; + return ; } Page.getLayout = (page) => { diff --git a/docs/src/components/banner/TableOfContentsBanner.tsx b/docs/src/components/banner/SideNavigationBanner.tsx similarity index 97% rename from docs/src/components/banner/TableOfContentsBanner.tsx rename to docs/src/components/banner/SideNavigationBanner.tsx index b6d16bf200a808..0e3e28778dad79 100644 --- a/docs/src/components/banner/TableOfContentsBanner.tsx +++ b/docs/src/components/banner/SideNavigationBanner.tsx @@ -3,7 +3,7 @@ import Typography from '@mui/material/Typography'; import { alpha } from '@mui/material/styles'; import { Link } from '@mui/docs/Link'; -export default function TableOfContentsBanner() { +export default function SideNavigationBanner() { return ( prop !== 'disableToc', + shouldForwardProp: (prop) => prop !== 'disableToc' && prop !== 'collapseToc', })(({ theme }) => ({ minHeight: '100vh', display: 'grid', @@ -39,19 +39,29 @@ const Main = styled('main', { }, }, { - props: ({ disableToc }) => !disableToc, + props: ({ disableToc, collapseToc }) => !disableToc && !collapseToc, style: { [theme.breakpoints.up('xl')]: { gridTemplateColumns: `1fr ${TOC_WIDTH}px`, }, }, }, + { + props: ({ disableToc, collapseToc }) => !disableToc && collapseToc, + style: { + gridTemplateColumns: '1fr auto', + }, + }, ], })); const StyledAppContainer = styled(AppContainer, { shouldForwardProp: (prop) => - prop !== 'disableAd' && prop !== 'hasTabs' && prop !== 'disableToc' && prop !== 'container', + prop !== 'disableAd' && + prop !== 'hasTabs' && + prop !== 'disableToc' && + prop !== 'container' && + prop !== 'collapseToc', })(({ theme }) => { return { position: 'relative', @@ -135,6 +145,7 @@ export default function AppLayoutDocs(props) { // improves the UX. It's faster to transition, and you don't lose UI states, like scroll. disableLayout = false, disableToc = false, + collapseToc, container = 'narrow', hasTabs = false, location, @@ -170,7 +181,7 @@ export default function AppLayoutDocs(props) { description={description} card={card} /> -
    +
    {/* Render the TOCs first to avoid layout shift when the HTML is streamed. See https://jakearchibald.com/2014/dont-use-flexbox-for-page-layout/ for more details. @@ -180,11 +191,12 @@ export default function AppLayoutDocs(props) { hasTabs={hasTabs} disableToc={disableToc} container={container} + collapseToc={collapseToc} > {children} - {disableToc ? null : } + {disableToc ? null : }
    @@ -194,6 +206,7 @@ export default function AppLayoutDocs(props) { AppLayoutDocs.propTypes = { BannerComponent: PropTypes.elementType, + collapseToc: PropTypes.bool, cardOptions: PropTypes.shape({ description: PropTypes.string, title: PropTypes.string, diff --git a/docs/src/modules/components/AppNavDrawer.tsx b/docs/src/modules/components/AppNavDrawer.tsx index ad409625aede25..c9385ead3a764d 100644 --- a/docs/src/modules/components/AppNavDrawer.tsx +++ b/docs/src/modules/components/AppNavDrawer.tsx @@ -25,7 +25,7 @@ import PageContext, { ProductVersion } from 'docs/src/modules/components/PageCon import { useTranslate } from '@mui/docs/i18n'; import MuiProductSelector from 'docs/src/modules/components/MuiProductSelector'; import DiamondSponsors from 'docs/src/modules/components/DiamondSponsors'; -import TableOfContentsBanner from 'docs/src/components/banner/TableOfContentsBanner'; +import SideNavigationBanner from 'docs/src/components/banner/SideNavigationBanner'; import { MuiPage } from 'docs/src/MuiPage'; // TODO: Collapse should expose an API to customize the duration based on the height. @@ -488,6 +488,7 @@ export default function AppNavDrawer(props: AppNavDrawerProps) { pb: 5, overflowY: 'auto', flexGrow: 1, + scrollbarWidth: 'thin', }} > @@ -506,7 +507,7 @@ export default function AppNavDrawer(props: AppNavDrawerProps) { }} > - + diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js index 3ae857a62c6fc4..fe31d6a9a1e1ee 100644 --- a/docs/src/modules/components/AppTableOfContents.js +++ b/docs/src/modules/components/AppTableOfContents.js @@ -226,7 +226,6 @@ function TableOfContents({ toc, itemLink, onLinkClick }) { variant="caption" sx={{ fontWeight: 'normal', color: 'text.secondary', mt: 0.5 }} > - {/* eslint-disable-next-line material-ui/no-hardcoded-labels */} {"We're looking for React Engineers and other amazing roles-come find out more!"} @@ -273,9 +272,11 @@ TableOfContents.propTypes = { }; export default function AppTableOfContents(props) { - const { toc } = props; + const { toc, collapseToc } = props; const t = useTranslate(); + const collapseBreakpoint = 'xl'; + const items = React.useMemo(() => flatten(toc), [toc]); const [activeState, setActiveState] = React.useState(null); const [popperAnchorEl, setPopperAnchorEl] = React.useState(null); @@ -397,17 +398,29 @@ export default function AppTableOfContents(props) { ({ - position: 'fixed', - top: { - xs: `calc(var(--MuiDocs-header-height) + ${theme.spacing(1)})`, - lg: `calc(var(--MuiDocs-header-height) + ${theme.spacing(5)})`, - }, - right: { xs: theme.spacing(1.5), lg: theme.spacing(3) }, - zIndex: 10, - display: { xl: 'none' }, - })} + className={collapseToc ? undefined : 'mui-fixed'} + sx={(theme) => + collapseToc + ? { + position: 'sticky', + top: `calc(var(--MuiDocs-header-height) + ${theme.spacing(4)})`, + marginTop: 'var(--MuiDocs-header-height)', + height: 'fit-content', + py: 1, + display: 'flex', + justifyContent: 'center', + } + : { + position: 'fixed', + top: { + xs: `calc(var(--MuiDocs-header-height) + ${theme.spacing(1)})`, + lg: `calc(var(--MuiDocs-header-height) + ${theme.spacing(5)})`, + }, + right: { xs: theme.spacing(1.5), lg: theme.spacing(3) }, + zIndex: 10, + display: { [collapseBreakpoint]: 'none' }, + } + } > @@ -480,7 +493,7 @@ export default function AppTableOfContents(props) { )} -