Skip to content

Commit 34f3006

Browse files
committed
Add TableOfContents and Section skeleton
1 parent 1705889 commit 34f3006

File tree

5 files changed

+166
-6
lines changed

5 files changed

+166
-6
lines changed

src/components/App.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { Component } from 'react'
22
import logo from '../logo.svg'
33
import StarCount from './StarCount'
4+
import TableOfContents from './TableOfContents'
5+
import Section from './Section'
46

57
class App extends Component {
68
render() {
@@ -11,9 +13,8 @@ class App extends Component {
1113
<img src={logo} className="App-logo" alt="logo" />
1214
<h1 className="App-title">The GraphQL Guide</h1>
1315
</header>
14-
<p className="App-intro">
15-
To get started, edit <code>src/App.js</code>, and save to reload.
16-
</p>
16+
<TableOfContents />
17+
<Section />
1718
</div>
1819
)
1920
}

src/components/Section.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
import Skeleton from 'react-loading-skeleton'
3+
4+
const Section = ({ loading = true }) => (
5+
<section className="Section">
6+
<div className="Section-header-wrapper">
7+
<header className="Section-header">
8+
<h1>Title</h1>
9+
<h2>Subtitle</h2>
10+
</header>
11+
</div>
12+
<div className="Section-content">
13+
{loading ? <Skeleton count={7} /> : null}
14+
</div>
15+
</section>
16+
)
17+
18+
export default Section

src/components/TableOfContents.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import { graphql } from 'react-apollo'
4+
import gql from 'graphql-tag'
5+
import Skeleton from 'react-loading-skeleton'
6+
import { NavLink } from 'react-router-dom'
7+
import classNames from 'classnames'
8+
9+
import { slugify, withHyphens } from '../lib/helpers'
10+
11+
const LoadingSkeleton = () => (
12+
<div>
13+
<h1>
14+
<Skeleton />
15+
</h1>
16+
<Skeleton count={4} />
17+
</div>
18+
)
19+
20+
const TableOfContents = ({ chapters, loading }) => (
21+
<nav className="TableOfContents">
22+
{loading ? (
23+
<LoadingSkeleton />
24+
) : (
25+
<ul className="TableOfContents-chapters">
26+
{chapters.map(chapter => {
27+
const chapterIsNumbered = chapter.number !== null
28+
return (
29+
<li
30+
className={classNames({ numbered: chapterIsNumbered })}
31+
key={chapter.id}
32+
>
33+
<NavLink
34+
to={{
35+
pathname: slugify(chapter),
36+
state: { chapter, section: chapter.sections[0] }
37+
}}
38+
className="TableOfContents-chapter-link"
39+
activeClassName="active"
40+
isActive={(match, location) => {
41+
const rootPath = location.pathname.split('/')[1]
42+
return rootPath.includes(withHyphens(chapter.title))
43+
}}
44+
>
45+
{chapterIsNumbered && (
46+
<span className="TableOfContents-chapter-number">
47+
{chapter.number}
48+
</span>
49+
)}
50+
{chapter.title}
51+
</NavLink>
52+
{chapterIsNumbered && (
53+
<ul className="TableOfContents-sections">
54+
{chapter.sections.map(section => (
55+
<li key={section.number}>
56+
<NavLink
57+
to={{
58+
pathname: slugify(chapter, section),
59+
state: { chapter, section }
60+
}}
61+
className="TableOfContents-section-link"
62+
activeClassName="active"
63+
>
64+
{section.title}
65+
</NavLink>
66+
</li>
67+
))}
68+
</ul>
69+
)}
70+
</li>
71+
)
72+
})}
73+
</ul>
74+
)}
75+
</nav>
76+
)
77+
78+
TableOfContents.propTypes = {
79+
chapters: PropTypes.arrayOf(
80+
PropTypes.shape({
81+
id: PropTypes.number.isRequired,
82+
number: PropTypes.number,
83+
title: PropTypes.string.isRequired,
84+
sections: PropTypes.arrayOf(
85+
PropTypes.shape({
86+
id: PropTypes.string.isRequired,
87+
number: PropTypes.number.isRequired,
88+
title: PropTypes.string
89+
}).isRequired
90+
).isRequired
91+
}).isRequired
92+
),
93+
loading: PropTypes.bool.isRequired
94+
}
95+
96+
const CHAPTER_QUERY = gql`
97+
query ChapterQuery {
98+
chapters {
99+
id
100+
number
101+
title
102+
sections {
103+
id
104+
number
105+
title
106+
}
107+
}
108+
}
109+
`
110+
111+
const withData = graphql(CHAPTER_QUERY, {
112+
props: ({ data: { chapters, loading } }) => ({
113+
chapters,
114+
loading
115+
})
116+
})
117+
118+
export default withData(TableOfContents)

src/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { split } from 'apollo-link'
1010
import { WebSocketLink } from 'apollo-link-ws'
1111
import { createHttpLink } from 'apollo-link-http'
1212
import { getMainDefinition } from 'apollo-utilities'
13+
import { BrowserRouter } from 'react-router-dom'
1314

1415
const httpLink = createHttpLink({
1516
uri: 'https://api.graphql.guide/graphql'
@@ -36,9 +37,11 @@ const cache = new InMemoryCache()
3637
const client = new ApolloClient({ link, cache })
3738

3839
ReactDOM.render(
39-
<ApolloProvider client={client}>
40-
<App />
41-
</ApolloProvider>,
40+
<BrowserRouter>
41+
<ApolloProvider client={client}>
42+
<App />
43+
</ApolloProvider>
44+
</BrowserRouter>,
4245
document.getElementById('root')
4346
)
4447

src/lib/helpers.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const withHyphens = string => string.replace(/ /g, '-')
2+
3+
// generate paths of the form:
4+
// /Preface
5+
// /Introduction
6+
// /1-Understanding-GraphQL-through-REST/1-Introduction
7+
export const slugify = (chapter, section) => {
8+
if (!section) {
9+
if (chapter.number !== null) {
10+
// default to the first section
11+
section = chapter.sections[0]
12+
} else {
13+
return '/' + withHyphens(chapter.title)
14+
}
15+
}
16+
17+
const chapterSlug = chapter.number + '-' + withHyphens(chapter.title)
18+
const sectionSlug = section.number + '-' + withHyphens(section.title)
19+
return `/${chapterSlug}/${sectionSlug}`
20+
}

0 commit comments

Comments
 (0)