From a10956e37c09ca7e13800d1f23a5b8f1ff0c216b Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Wed, 1 Aug 2018 21:31:20 +0200 Subject: [PATCH] [docs] Add a Table of contents --- docs/src/modules/components/AppContent.js | 21 +- docs/src/modules/components/AppDrawer.js | 6 +- docs/src/modules/components/AppFrame.js | 5 +- docs/src/modules/components/AppSearch.js | 2 - .../modules/components/AppTableOfContents.js | 183 ++++++++++++++++++ docs/src/modules/components/Link.js | 10 +- docs/src/modules/components/MarkdownDocs.js | 43 ++-- docs/src/pages/demos/lists/lists.md | 10 +- docs/src/pages/getting-started/faq/faq.md | 6 +- .../src/pages/premium-themes/PremiumThemes.js | 4 +- docs/src/pages/style/icons/icons.md | 4 +- docs/src/pages/style/typography/typography.md | 4 +- .../src/MarkdownElement/MarkdownElement.js | 13 +- 13 files changed, 258 insertions(+), 53 deletions(-) create mode 100644 docs/src/modules/components/AppTableOfContents.js diff --git a/docs/src/modules/components/AppContent.js b/docs/src/modules/components/AppContent.js index 4a4f73bc35e280..f8735eb16c43cb 100644 --- a/docs/src/modules/components/AppContent.js +++ b/docs/src/modules/components/AppContent.js @@ -4,15 +4,24 @@ import classNames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; const styles = theme => ({ - root: theme.mixins.gutters({ + root: { paddingTop: 80, flex: '1 1 100%', maxWidth: '100%', margin: '0 auto', - }), - [theme.breakpoints.up('md')]: { - root: { - maxWidth: theme.breakpoints.values.md, + paddingLeft: theme.spacing.unit * 2, + paddingRight: theme.spacing.unit * 2, + [theme.breakpoints.up('sm')]: { + paddingLeft: theme.spacing.unit * 4, + paddingRight: theme.spacing.unit * 4, + }, + [theme.breakpoints.up('md')]: { + maxWidth: 'calc(100% - 160px)', + }, + [theme.breakpoints.up('lg')]: { + paddingLeft: theme.spacing.unit * 5, + paddingRight: theme.spacing.unit * 9, + maxWidth: 'calc(100% - 240px - 160px)', }, }, }); @@ -20,7 +29,7 @@ const styles = theme => ({ function AppContent(props) { const { className, classes, children } = props; - return
{children}
; + return
{children}
; } AppContent.propTypes = { diff --git a/docs/src/modules/components/AppDrawer.js b/docs/src/modules/components/AppDrawer.js index d45e385459b4ca..9dd1f8ff3ce870 100644 --- a/docs/src/modules/components/AppDrawer.js +++ b/docs/src/modules/components/AppDrawer.js @@ -14,7 +14,7 @@ import { pageToTitle } from 'docs/src/modules/utils/helpers'; const styles = theme => ({ paper: { - width: 250, + width: 240, backgroundColor: theme.palette.background.paper, }, title: { @@ -117,7 +117,7 @@ function AppDrawer(props, context) { ); return ( -
+
+ ); } diff --git a/docs/src/modules/components/AppFrame.js b/docs/src/modules/components/AppFrame.js index a3c01d16178368..f43d3ee9cdc105 100644 --- a/docs/src/modules/components/AppFrame.js +++ b/docs/src/modules/components/AppFrame.js @@ -60,12 +60,13 @@ const styles = theme => ({ }, appBarShift: { [theme.breakpoints.up('lg')]: { - width: 'calc(100% - 250px)', + width: 'calc(100% - 240px)', }, }, drawer: { [theme.breakpoints.up('lg')]: { - width: 250, + flexShrink: 0, + width: 240, }, }, navIconHide: { diff --git a/docs/src/modules/components/AppSearch.js b/docs/src/modules/components/AppSearch.js index 04ba13f7251658..e0800edd9b1c9e 100644 --- a/docs/src/modules/components/AppSearch.js +++ b/docs/src/modules/components/AppSearch.js @@ -1,7 +1,6 @@ import React from 'react'; import keycode from 'keycode'; import compose from 'recompose/compose'; -import pure from 'recompose/pure'; import EventListener from 'react-event-listener'; import PropTypes from 'prop-types'; import Router from 'next/router'; @@ -196,5 +195,4 @@ AppSearch.propTypes = { export default compose( withStyles(styles), withWidth(), - pure, )(AppSearch); diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js new file mode 100644 index 00000000000000..1a0f6d193c126f --- /dev/null +++ b/docs/src/modules/components/AppTableOfContents.js @@ -0,0 +1,183 @@ +/* eslint-disable react/no-danger */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import Link from 'docs/src/modules/components/Link'; +import marked from 'marked'; +import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb. +import EventListener from 'react-event-listener'; +import { withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import { textToHash } from '@material-ui/docs/MarkdownElement/MarkdownElement'; + +const renderer = new marked.Renderer(); + +let itemsServer = null; +renderer.heading = (text, level) => { + if (level === 1 || level > 3) { + return; + } + + if (level === 2) { + itemsServer.push({ + text, + level, + hash: textToHash(text), + children: [], + }); + } + + if (level === 3) { + itemsServer[itemsServer.length - 1].children.push({ + text, + level, + hash: textToHash(text), + }); + } +}; + +const styles = theme => ({ + root: { + top: 70, + width: 160, + flexShrink: 0, + order: 2, + position: 'sticky', + wordBreak: 'break-word', + height: 'calc(100vh - 70px)', + overflowY: 'auto', + padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 2}px ${theme.spacing.unit * + 2}px 0`, + display: 'none', + [theme.breakpoints.up('md')]: { + display: 'block', + }, + }, + ul: { + padding: 0, + margin: 0, + listStyleType: 'none', + }, + item: { + fontSize: 13, + padding: `${theme.spacing.unit / 2}px 0`, + }, +}); + +class AppTableOfContents extends React.Component { + handleScroll = debounce(() => { + this.findActiveIndex(); + }, 166); // Corresponds to 10 frames at 60 Hz. + + constructor(props) { + super(props); + itemsServer = []; + marked(props.contents.join(''), { + renderer, + }); + } + + state = { + active: null, + }; + + componentDidMount() { + this.itemsClient = []; + + itemsServer.forEach(item2 => { + this.itemsClient.push({ + ...item2, + node: document.getElementById(item2.hash), + }); + + if (item2.children.length > 0) { + item2.children.forEach(item3 => { + this.itemsClient.push({ + ...item3, + node: document.getElementById(item3.hash), + }); + }); + } + }); + + this.findActiveIndex(); + } + + componentWillUnmount() { + this.handleScroll.clear(); + } + + findActiveIndex = () => { + const active = this.itemsClient.find( + (item, index) => + document.documentElement.scrollTop < item.node.offsetTop + 100 || + index === this.itemsClient.length - 1, + ); + + if (active && this.state.active !== active.hash) { + this.setState({ + active: active.hash, + }); + } + }; + + render() { + const { classes } = this.props; + const { active } = this.state; + + return ( + + ); + } +} + +AppTableOfContents.propTypes = { + classes: PropTypes.object.isRequired, + contents: PropTypes.array.isRequired, +}; + +export default withStyles(styles)(AppTableOfContents); diff --git a/docs/src/modules/components/Link.js b/docs/src/modules/components/Link.js index b3accc8ae1f3de..cf89e07dc69c69 100644 --- a/docs/src/modules/components/Link.js +++ b/docs/src/modules/components/Link.js @@ -47,7 +47,13 @@ function Link(props) { } = props; let ComponentRoot; - const className = classNames(classes.root, classes[variant], classNameProp); + const className = classNames( + classes.root, + { + [classes[variant]]: variant !== 'inherit', + }, + classNameProp, + ); let RootProps; let children = childrenProp; @@ -103,7 +109,7 @@ Link.propTypes = { router: PropTypes.shape({ pathname: PropTypes.string.isRequired, }).isRequired, - variant: PropTypes.oneOf(['default', 'primary', 'secondary', 'button']), + variant: PropTypes.oneOf(['default', 'primary', 'secondary', 'button', 'inherit']), }; export default compose( diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js index bf21716f4fdd2d..ffa57502478f12 100644 --- a/docs/src/modules/components/MarkdownDocs.js +++ b/docs/src/modules/components/MarkdownDocs.js @@ -10,6 +10,7 @@ import AppContent from 'docs/src/modules/components/AppContent'; import Demo from 'docs/src/modules/components/Demo'; import Carbon from 'docs/src/modules/components/Carbon'; import AppFrame from 'docs/src/modules/components/AppFrame'; +import AppTableOfContents from 'docs/src/modules/components/AppTableOfContents'; import { getHeaders, getContents, @@ -55,15 +56,30 @@ function MarkdownDocs(props, context) { } } - const section = markdownLocation.split('/')[4]; + if (headers.components.length > 0) { + const section = markdownLocation.split('/')[4]; + contents.push(` +## API + +${headers.components + .map( + component => + `- [<${component} />](${section === 'lab' ? '/lab/api' : '/api'}/${kebabCase( + component, + )})`, + ) + .join('\n')} + `); + } return ( + + -