Skip to content

Commit

Permalink
[docs] Add a Table of contents (#12368)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari authored Aug 12, 2018
1 parent e14c490 commit 20ad6da
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 54 deletions.
19 changes: 13 additions & 6 deletions docs/src/modules/components/AppContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@ 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,
maxWidth: 'calc(100% - 160px)',
},
[theme.breakpoints.up('lg')]: {
paddingLeft: theme.spacing.unit * 5,
paddingRight: theme.spacing.unit * 9,
maxWidth: 'calc(100% - 240px - 160px)',
},
},
});

function AppContent(props) {
const { className, classes, children } = props;

return <div className={classNames(classes.root, className)}>{children}</div>;
return <main className={classNames(classes.root, className)}>{children}</main>;
}

AppContent.propTypes = {
Expand Down
6 changes: 3 additions & 3 deletions docs/src/modules/components/AppDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -117,7 +117,7 @@ function AppDrawer(props, context) {
);

return (
<div className={className}>
<nav className={className}>
<Hidden lgUp={!disablePermanent} implementation="js">
<SwipeableDrawer
classes={{
Expand Down Expand Up @@ -148,7 +148,7 @@ function AppDrawer(props, context) {
</Drawer>
</Hidden>
)}
</div>
</nav>
);
}

Expand Down
5 changes: 3 additions & 2 deletions docs/src/modules/components/AppFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 0 additions & 2 deletions docs/src/modules/components/AppSearch.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -196,5 +195,4 @@ AppSearch.propTypes = {
export default compose(
withStyles(styles),
withWidth(),
pure,
)(AppSearch);
183 changes: 183 additions & 0 deletions docs/src/modules/components/AppTableOfContents.js
Original file line number Diff line number Diff line change
@@ -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('sm')]: {
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 (
<nav className={classes.root}>
{itemsServer.length > 0 ? (
<React.Fragment>
<Typography variant="body2" gutterBottom>
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,
};

export default withStyles(styles)(AppTableOfContents);
10 changes: 8 additions & 2 deletions docs/src/modules/components/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand Down
43 changes: 21 additions & 22 deletions docs/src/modules/components/MarkdownDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 =>
`- [&lt;${component} /&gt;](${section === 'lab' ? '/lab/api' : '/api'}/${kebabCase(
component,
)})`,
)
.join('\n')}
`);
}

return (
<AppFrame>
<Head
title={`${headers.title || getTitle(markdown)} - Material-UI`}
description={getDescription(markdown)}
/>
<AppTableOfContents contents={contents} />
<AppContent className={classes.root}>
<Head
title={`${headers.title || getTitle(markdown)} - Material-UI`}
description={getDescription(markdown)}
/>
<div className={classes.header}>
<Button component="a" href={`${SOURCE_CODE_ROOT_URL}${markdownLocation}`}>
{'Edit this page'}
Expand Down Expand Up @@ -94,23 +110,6 @@ function MarkdownDocs(props, context) {
<MarkdownElement className={classes.markdownElement} key={content} text={content} />
);
})}
{headers.components.length > 0 ? (
<MarkdownElement
className={classes.markdownElement}
text={`
## API
${headers.components
.map(
component =>
`- [&lt;${component} /&gt;](${
section === 'lab' ? '/lab/api' : '/api'
}/${kebabCase(component)})`,
)
.join('\n')}
`}
/>
) : null}
</AppContent>
</AppFrame>
);
Expand Down
10 changes: 5 additions & 5 deletions docs/src/pages/demos/lists/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ components: Collapse, Divider, List, ListItem, ListItemAvatar, ListItemIcon, Lis

[Lists](https://material.io/design/components/lists.html) are a continuous group of text or images. They are composed of items containing primary and supplemental actions, which are represented by icons and text.

### Simple List
## Simple List

{{"demo": "pages/demos/lists/SimpleList.js"}}

### Folder List
## Folder List

{{"demo": "pages/demos/lists/FolderList.js"}}

### Inset List
## Inset List

{{"demo": "pages/demos/lists/InsetList.js"}}

### Nested List
## Nested List

{{"demo": "pages/demos/lists/NestedList.js"}}

### Pinned Subheader List
## Pinned Subheader List

Upon scrolling, subheaders remain pinned to the top of the screen until pushed off screen by the next subheader.

Expand Down
Loading

0 comments on commit 20ad6da

Please sign in to comment.