UNPKG

materialuiupgraded

Version:

Material-UI's workspace package

191 lines (171 loc) 5.32 kB
/* 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 throttle from 'lodash/throttle'; 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'; import Ad from 'docs/src/modules/components/Ad'; const renderer = new marked.Renderer(); let itemsServer = null; renderer.heading = (text, level) => { if (level === 2) { itemsServer.push({ text, level, hash: textToHash(text), children: [], }); } else if (level === 3) { itemsServer[itemsServer.length - 1].children.push({ text, level, hash: textToHash(text), }); } }; const styles = theme => ({ root: { top: 70, width: 162, 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('sm')]: { display: 'block', }, }, contents: { marginTop: theme.spacing.unit * 2, }, ul: { padding: 0, margin: 0, listStyleType: 'none', }, item: { fontSize: 13, padding: `${theme.spacing.unit / 2}px 0`, }, }); class AppTableOfContents extends React.Component { handleScroll = throttle(() => { 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.cancel(); } findActiveIndex = () => { let active; for (let i = 0; i < this.itemsClient.length; i += 1) { const item = this.itemsClient[i]; if ( document.documentElement.scrollTop < item.node.offsetTop + 100 || i === this.itemsClient.length - 1 ) { active = item; break; } } if (active && this.state.active !== active.hash) { this.setState({ active: active.hash, }); } }; render() { const { classes, disableAd } = this.props; const { active } = this.state; return ( <nav className={classes.root}> {disableAd ? null : <Ad />} {itemsServer.length > 0 ? ( <React.Fragment> <Typography gutterBottom className={classes.contents}> Contents </Typography> <EventListener target="window" onScroll={this.handleScroll} /> <ul className={classes.ul}> {itemsServer.map(item2 => ( <li key={item2.text}> <Typography color={active === item2.hash ? 'textPrimary' : 'textSecondary'} className={classes.item} component={linkProps => ( <Link {...linkProps} variant="inherit" href={`#${item2.hash}`} /> )} > <span dangerouslySetInnerHTML={{ __html: item2.text }} /> </Typography> {item2.children.length > 0 ? ( <ul className={classes.ul}> {item2.children.map(item3 => ( <li key={item3.text}> <Typography className={classes.item} style={{ paddingLeft: 8 * 2, }} color={active === item3.hash ? 'textPrimary' : 'textSecondary'} component={linkProps => ( <Link {...linkProps} variant="inherit" href={`#${item3.hash}`} /> )} > <span dangerouslySetInnerHTML={{ __html: item3.text }} /> </Typography> </li> ))} </ul> ) : null} </li> ))} </ul> </React.Fragment> ) : null} </nav> ); } } AppTableOfContents.propTypes = { classes: PropTypes.object.isRequired, contents: PropTypes.array.isRequired, disableAd: PropTypes.bool.isRequired, }; export default withStyles(styles)(AppTableOfContents);