Skip to content

Commit 252b2ca

Browse files
m-allansonjastack
authored andcommitted
Add search form to site (gatsbyjs#3421)
* Add search form to site header * Prevent sidebar from overlapping search results * Override default search result styles * Reduce size of mask image * Prettier * Add a class name for the DocSearch crawlers As mentioned in the DocSearch signup message: gatsbyjs#3097 (comment) * Disable DocSearch's debug mode * Capture default navigation events, replace with client-side navigation * Add a second identifier class for DocSearch * Improve mobile styles * Improve styling - Increase specificity so styles work in production build - Tidy up layout at medium and small breakpoints - Prettier * Load external CSS after document body
1 parent 10cc9d2 commit 252b2ca

File tree

6 files changed

+258
-11
lines changed

6 files changed

+258
-11
lines changed

www/src/components/navigation.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Link from "gatsby-link"
33
import GithubIcon from "react-icons/lib/go/mark-github"
44
import TwitterIcon from "react-icons/lib/fa/twitter"
55

6+
import SearchForm from "../components/search-form"
67
import DiscordIcon from "../components/discord"
78
import logo from "../gatsby-negative.svg"
89
import typography, { rhythm, scale } from "../utils/typography"
@@ -117,30 +118,31 @@ export default ({ pathname }) => {
117118
<Link
118119
to="/"
119120
css={{
121+
alignItems: `center`,
120122
color: `inherit`,
121-
display: `inline-block`,
123+
display: `flex`,
122124
textDecoration: `none`,
123125
marginRight: rhythm(0.5),
124126
}}
125127
>
126128
<img
127129
src={logo}
128130
css={{
129-
display: `inline-block`,
130131
height: rhythm(1.2),
131132
width: rhythm(1.2),
132133
margin: 0,
133134
marginRight: rhythm(2 / 4),
134-
verticalAlign: `middle`,
135135
}}
136136
alt=""
137137
/>
138138
<h1
139139
css={{
140140
...scale(2 / 5),
141-
display: `inline-block`,
142141
margin: 0,
143-
verticalAlign: `middle`,
142+
display: `none`,
143+
[presets.Mobile]: {
144+
display: `block`,
145+
},
144146
}}
145147
>
146148
Gatsby
@@ -150,10 +152,15 @@ export default ({ pathname }) => {
150152
css={{
151153
display: `none`,
152154
[presets.Tablet]: {
153-
display: `block`,
155+
display: `flex`,
154156
margin: 0,
155157
padding: 0,
156158
listStyle: `none`,
159+
flexGrow: 1,
160+
overflowX: `auto`,
161+
maskImage: `linear-gradient(to right, transparent, white ${rhythm(
162+
1 / 8
163+
)}, white 98%, transparent)`,
157164
},
158165
}}
159166
>
@@ -165,12 +172,13 @@ export default ({ pathname }) => {
165172
</ul>
166173
<div
167174
css={{
168-
marginLeft: isHomepage ? rhythm(1 / 2) : `auto`,
169-
[presets.Phablet]: {
170-
marginLeft: isHomepage ? `auto` : `auto`,
171-
},
175+
display: `flex`,
176+
marginLeft: `auto`,
172177
}}
173178
>
179+
{!isHomepage && (
180+
<SearchForm key="SearchForm" styles={{ ...navItemStyles }} />
181+
)}
174182
<a
175183
href="https://github.com/gatsbyjs/gatsby"
176184
title="GitHub"

www/src/components/search-form.js

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import React, { Component } from "react"
2+
import PropTypes from "prop-types"
3+
import { navigateTo } from "gatsby-link"
4+
import { rhythm } from "../utils/typography"
5+
import presets from "../utils/presets"
6+
7+
import { css } from "glamor"
8+
9+
// Override default search result styles
10+
css.global(`.searchWrap .algolia-docsearch-suggestion--highlight`, {
11+
backgroundColor: `${presets.lightPurple} !important`,
12+
boxShadow: `inset 0 -2px 0 0 ${presets.lightPurple} !important`,
13+
color: `black`,
14+
fontWeight: `bold`,
15+
})
16+
css.global(`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column`, {
17+
width: `100% !important`,
18+
})
19+
css.global(`.searchWrap .algolia-docsearch-suggestion--subcategory-column-text:after`, {
20+
display: `none`,
21+
})
22+
css.global(
23+
`.searchWrap .algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion:not(.suggestion-layout-simple) .algolia-docsearch-suggestion--content`,
24+
{ backgroundColor: `${presets.brandLighter} !important` }
25+
)
26+
css.global(
27+
`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content.algolia-docsearch-suggestion--no-results`,
28+
{
29+
maxWidth: `100%`,
30+
paddingLeft: `0 !important`,
31+
width: `100% !important`,
32+
}
33+
)
34+
css.global(
35+
`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content.algolia-docsearch-suggestion--no-results:before`,
36+
{ display: `none !important` }
37+
)
38+
css.global(`.searchWrap .algolia-autocomplete .ds-dropdown-menu`, {
39+
position: `fixed !important`,
40+
top: `${rhythm(2)} !important`,
41+
left: `${rhythm(0.5)} !important`,
42+
right: `${rhythm(0.5)} !important`,
43+
minWidth: `calc(100vw - ${rhythm(1)})`,
44+
maxWidth: `calc(100vw - 2rem)`,
45+
maxHeight: `calc(100vh - 5rem)`,
46+
display: `block`,
47+
})
48+
css.global(
49+
`.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu, .algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu`,
50+
{
51+
left: `${rhythm(0.5)} !important`,
52+
right: `${rhythm(0.5)} !important`,
53+
}
54+
)
55+
css.global(
56+
`.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu::before`,
57+
{
58+
right: rhythm(5),
59+
}
60+
)
61+
css.global(
62+
`.searchWrap .algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu::before`,
63+
{
64+
left: rhythm(7),
65+
}
66+
)
67+
68+
// use css.insert() for media query with global CSS
69+
css.insert(`@media ${presets.phablet}{
70+
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column {
71+
font-weight: 400;
72+
width: 30% !important;
73+
text-align: right;
74+
opacity: 1;
75+
padding: ${rhythm(0.5)} ${rhythm(1)} ${rhythm(0.5)} 0;
76+
}
77+
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:before {
78+
content: "";
79+
position: absolute;
80+
display: block !important;
81+
top: 0;
82+
height: 100%;
83+
width: 1px;
84+
background: #ddd;
85+
right: 0;
86+
}
87+
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:after {
88+
display: none;
89+
}
90+
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--content {
91+
width: 70% !important;
92+
max-width: 70%;
93+
display: block;
94+
padding: ${rhythm(0.5)} 0 ${rhythm(0.5)} ${rhythm(1)} !important;
95+
}
96+
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--content:before {
97+
content: "";
98+
position: absolute;
99+
display: block !important;
100+
top: 0;
101+
height: 100%;
102+
width: 1px;
103+
background: #ddd;
104+
left: -1px;
105+
}
106+
}`)
107+
108+
css.insert(`@media ${presets.tablet}{
109+
.searchWrap .algolia-autocomplete .ds-dropdown-menu {
110+
top: 100% !important;
111+
position: absolute !important;
112+
max-width: 600px !important;
113+
min-width: 500px !important;
114+
}
115+
.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu {
116+
right: 0 !important;
117+
left: inherit !important;
118+
}
119+
.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu::before {
120+
right: ${rhythm(2)};
121+
}
122+
}`)
123+
124+
class SearchForm extends Component {
125+
constructor() {
126+
super()
127+
this.state = { enabled: true }
128+
}
129+
130+
/**
131+
* Replace the default selection event, allowing us to do client-side
132+
* navigation thus avoiding a full page refresh.
133+
*
134+
* Ref: https://github.com/algolia/autocomplete.js#events
135+
*/
136+
autocompleteSelected(e) {
137+
e.stopPropagation()
138+
// Use an anchor tag to parse the absolute url (from autocomplete.js) into a relative url
139+
// eslint-disable-next-line no-undef
140+
const a = document.createElement(`a`)
141+
a.href = e._args[0].url
142+
navigateTo(`${a.pathname}${a.hash}`)
143+
}
144+
145+
componentDidMount() {
146+
if (
147+
typeof window === `undefined` || // eslint-disable-line no-undef
148+
typeof window.docsearch === `undefined` // eslint-disable-line no-undef
149+
) {
150+
console.warn(`Search has failed to load and now is being disabled`)
151+
this.setState({ enabled: false })
152+
return
153+
}
154+
155+
// eslint-disable-next-line no-undef
156+
window.addEventListener(
157+
`autocomplete:selected`,
158+
this.autocompleteSelected,
159+
true
160+
)
161+
162+
// eslint-disable-next-line no-undef
163+
window.docsearch({
164+
apiKey: `71af1f9c4bd947f0252e17051df13f9c`,
165+
indexName: `gatsbyjs`,
166+
inputSelector: `#doc-search`,
167+
debug: false,
168+
})
169+
}
170+
171+
render() {
172+
const { enabled } = this.state
173+
const { styles } = this.props.styles
174+
return enabled ? (
175+
<form
176+
css={{
177+
...styles,
178+
display: `flex`,
179+
flex: `0 0 auto`,
180+
flexDirection: `row`,
181+
alignItems: `center`,
182+
marginLeft: rhythm(1 / 2),
183+
marginBottom: 0,
184+
}}
185+
className="searchWrap"
186+
>
187+
<input
188+
id="doc-search"
189+
css={{
190+
appearance: `none`,
191+
background: `transparent`,
192+
border: 0,
193+
color: presets.brand,
194+
paddingTop: rhythm(1 / 8),
195+
paddingRight: rhythm(1 / 4),
196+
paddingBottom: rhythm(1 / 8),
197+
paddingLeft: rhythm(1),
198+
backgroundImage: `url(/search.svg)`,
199+
backgroundSize: `16px 16px`,
200+
backgroundRepeat: `no-repeat`,
201+
backgroundPositionY: `center`,
202+
backgroundPositionX: `5px`,
203+
overflow: `hidden`,
204+
width: rhythm(1),
205+
transition: `width 0.2s ease`,
206+
207+
":focus": {
208+
outline: 0,
209+
backgroundColor: presets.brandLighter,
210+
borderRadius: presets.radiusLg,
211+
width: rhythm(5),
212+
},
213+
214+
[presets.Desktop]: {
215+
width: rhythm(5),
216+
},
217+
}}
218+
type="search"
219+
placeholder="Search docs"
220+
aria-label="Search docs"
221+
/>
222+
</form>
223+
) : null
224+
}
225+
}
226+
227+
SearchForm.propTypes = {
228+
styles: PropTypes.object,
229+
}
230+
231+
export default SearchForm

www/src/components/sidebar-body.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class SidebarBody extends React.Component {
189189
css={{
190190
padding: isInline ? 0 : rhythm(3 / 4),
191191
}}
192+
className="docSearch-sidebar"
192193
>
193194
{menu.map((section, index) => (
194195
<div

www/src/html.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export default class HTML extends React.Component {
7272
href={`/safari-pinned-tab.svg`}
7373
color="#5bbad5"
7474
/>
75+
<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
7576
{css}
7677
</head>
7778
<body {...this.props.bodyAttributes}>
@@ -80,6 +81,7 @@ export default class HTML extends React.Component {
8081
dangerouslySetInnerHTML={{ __html: this.props.body }}
8182
/>
8283
{this.props.postBodyComponents}
84+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
8385
</body>
8486
</html>
8587
)

www/src/layouts/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class DefaultLayout extends React.Component {
3030
this.props.location.pathname.slice(0, 10) === `/packages/` ||
3131
this.props.location.pathname.slice(0, 10) === `/tutorial/` ||
3232
this.props.location.pathname.slice(0, 9) === `/features`
33+
const isSearchSource = hasSidebar
3334
const sidebarStyles = {
3435
borderRight: `1px solid ${colors.b[0]}`,
3536
backgroundColor: presets.sidebar,
@@ -45,7 +46,6 @@ class DefaultLayout extends React.Component {
4546
position: `fixed`,
4647
top: `calc(${presets.headerHeight} - 1px)`,
4748
overflowY: `auto`,
48-
zIndex: 1,
4949
height: `calc(100vh - ${presets.headerHeight} + 1px)`,
5050
WebkitOverflowScrolling: `touch`,
5151
"::-webkit-scrollbar": {
@@ -136,6 +136,7 @@ class DefaultLayout extends React.Component {
136136
paddingLeft: hasSidebar ? rhythm(12) : 0,
137137
},
138138
}}
139+
className={isSearchSource && `docSearch-content`}
139140
>
140141
{this.props.children()}
141142
</div>

www/static/search.svg

Lines changed: 4 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)