From 19aa8ad0071baa286319fcd946b1801a8c54f1bd Mon Sep 17 00:00:00 2001 From: Matt Brookes Date: Fri, 15 Feb 2019 09:50:23 +0000 Subject: [PATCH] [docs] Update the styling of the TOC (#14520) * [docs] Update the styling of the TOC (Just scratching an itch :smile:) * Update the TOC on click of #, update the URL * no hash for title * Hide subitems for sections other than current * new selection strategy * fix duplicate id * change offset * move styles back * Collapse transition * Revert the use of - * Refactor styles * Revert "Collapse transition" This reverts commit 6e8fd9766d99d0d0ebaa36a83ada987bc405cc8f. * don't hide sub-headings * prettier * more subtle styling * no smooth scrolling * fix scroll position on page change * Don't use state for tracking clicked links * tweak the intendation & spacing * prettier * small change --- docs/src/modules/components/AppContent.js | 4 +- .../modules/components/AppTableOfContents.js | 99 ++++++++++++++++--- docs/src/pages/style/icons/FontAwesome.js | 2 +- .../src/MarkdownElement/MarkdownElement.js | 2 +- pages/_document.js | 2 +- 5 files changed, 91 insertions(+), 18 deletions(-) diff --git a/docs/src/modules/components/AppContent.js b/docs/src/modules/components/AppContent.js index 2151079efcfb62..32b1d5e1d47363 100644 --- a/docs/src/modules/components/AppContent.js +++ b/docs/src/modules/components/AppContent.js @@ -12,9 +12,9 @@ const styles = theme => ({ paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2), [theme.breakpoints.up('sm')]: { - paddingLeft: theme.spacing(4), + paddingLeft: theme.spacing(3), paddingRight: theme.spacing(4), - maxWidth: 'calc(100% - 167px)', + maxWidth: 'calc(100% - 175px)', }, [theme.breakpoints.up('lg')]: { paddingLeft: theme.spacing(5), diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js index fc0e026a71ef7b..2c694cc9f7700b 100644 --- a/docs/src/modules/components/AppTableOfContents.js +++ b/docs/src/modules/components/AppTableOfContents.js @@ -6,6 +6,7 @@ import marked from 'marked'; import warning from 'warning'; import throttle from 'lodash/throttle'; import EventListener from 'react-event-listener'; +import clsx from 'clsx'; import { withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import { textToHash } from '@material-ui/docs/MarkdownElement/MarkdownElement'; @@ -48,14 +49,14 @@ const styles = theme => ({ top: 70 + 29, // Fix IE 11 position sticky issue. marginTop: 70 + 29, - width: 167, + width: 175, flexShrink: 0, order: 2, position: 'sticky', wordBreak: 'break-word', - height: 'calc(100vh - 70px)', + height: 'calc(100vh - 70px - 29px)', overflowY: 'auto', - padding: `${theme.spacing(2)}px ${theme.spacing(2)}px ${theme.spacing(2)}px 5px`, + padding: theme.spacing(2, 2, 2, 0), display: 'none', [theme.breakpoints.up('sm')]: { display: 'block', @@ -71,8 +72,24 @@ const styles = theme => ({ }, item: { fontSize: 13, - padding: theme.spacing(0.5, 0), + padding: theme.spacing(0.5, 0, 0.5, 1), + borderLeft: '4px solid transparent', + boxSizing: 'content-box', + '&:hover': { + borderLeft: `4px solid ${ + theme.palette.type === 'light' ? theme.palette.grey[200] : theme.palette.grey[900] + }`, + }, + '&$active': { + borderLeft: `4px solid ${ + theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[800] + }`, + }, + }, + secondaryItem: { + paddingLeft: theme.spacing(2.5), }, + active: {}, }); function checkDuplication(uniq, item) { @@ -88,6 +105,8 @@ class AppTableOfContents extends React.Component { this.findActiveIndex(); }, 166); // Corresponds to 10 frames at 60 Hz. + clicked = false; + constructor(props) { super(); this.itemsServer = getItems(props.contents); @@ -118,25 +137,49 @@ class AppTableOfContents extends React.Component { }); } }); - this.findActiveIndex(); + window.addEventListener('hashchange', this.handleHashChange); } componentWillUnmount() { this.handleScroll.cancel(); + clearTimeout(this.unsetClicked); + window.removeEventListener('hashchange', this.handleHashChange); } + // Update the active TOC entry if the hash changes through click on '#' icon + handleHashChange = () => { + const hash = window.location.hash.substring(1); + + if (this.state.active !== hash) { + this.setState({ + active: hash, + }); + } + }; + findActiveIndex = () => { + // Don't set the active index based on scroll if a link was just clicked + if (this.clicked) { + return; + } + let active; - for (let i = 0; i < this.itemsClient.length; i += 1) { + for (let i = this.itemsClient.length - 1; i >= 0; i -= 1) { + // No hash if we're near the top of the page + if (document.documentElement.scrollTop < 200) { + active = { hash: null }; + break; + } + const item = this.itemsClient[i]; warning(item.node, `Missing node on the item ${JSON.stringify(item, null, 2)}`); if ( item.node && - (document.documentElement.scrollTop < item.node.offsetTop + 100 || - i === this.itemsClient.length - 1) + item.node.offsetTop < + document.documentElement.scrollTop + document.documentElement.clientHeight / 8 ) { active = item; break; @@ -147,6 +190,28 @@ class AppTableOfContents extends React.Component { this.setState({ active: active.hash, }); + + window.history.replaceState( + null, + null, + active.hash === null + ? `${window.location.pathname}${window.location.search}` + : `#${active.hash}`, + ); + } + }; + + handleClick = hash => () => { + // Used to disable findActiveIndex if the page scrolls due to a click + this.clicked = true; + this.unsetClicked = setTimeout(() => { + this.clicked = false; + }, 1000); + + if (this.state.active !== hash) { + this.setState({ + active: hash, + }); } }; @@ -169,7 +234,12 @@ class AppTableOfContents extends React.Component { block color={active === item2.hash ? 'textPrimary' : 'textSecondary'} href={`#${item2.hash}`} - className={classes.item} + underline="none" + onClick={this.handleClick(item2.hash)} + className={clsx( + classes.item, + active === item2.hash ? classes.active : undefined, + )} > @@ -181,10 +251,13 @@ class AppTableOfContents extends React.Component { block color={active === item3.hash ? 'textPrimary' : 'textSecondary'} href={`#${item3.hash}`} - className={classes.item} - style={{ - paddingLeft: 8 * 2, - }} + underline="none" + onClick={this.handleClick(item3.hash)} + className={clsx( + classes.item, + classes.secondaryItem, + active === item3.hash ? classes.active : undefined, + )} > diff --git a/docs/src/pages/style/icons/FontAwesome.js b/docs/src/pages/style/icons/FontAwesome.js index ca4f617b938d21..81fb0964956a26 100644 --- a/docs/src/pages/style/icons/FontAwesome.js +++ b/docs/src/pages/style/icons/FontAwesome.js @@ -27,7 +27,7 @@ class FontAwesome extends React.Component { componentDidMount() { loadCSS( 'https://use.fontawesome.com/releases/v5.1.0/css/all.css', - document.querySelector('#font-awesome'), + document.querySelector('#font-awesome-css'), ); } diff --git a/packages/material-ui-docs/src/MarkdownElement/MarkdownElement.js b/packages/material-ui-docs/src/MarkdownElement/MarkdownElement.js index 51ac62671ab32d..0cee732e16dd92 100644 --- a/packages/material-ui-docs/src/MarkdownElement/MarkdownElement.js +++ b/packages/material-ui-docs/src/MarkdownElement/MarkdownElement.js @@ -105,7 +105,7 @@ const styles = theme => ({ fontSize: 16, color: theme.palette.text.primary, '& .anchor-link': { - marginTop: -96, // Offset for the anchor. + marginTop: -96 - 29, // Offset for the anchor. position: 'absolute', }, '& pre, & pre[class*="language-"]': { diff --git a/pages/_document.js b/pages/_document.js index 08f8b442b35e84..a03a8b4c93d634 100644 --- a/pages/_document.js +++ b/pages/_document.js @@ -60,7 +60,7 @@ class MyDocument extends Document { */}