Skip to content

Commit

Permalink
Add search form to site (gatsbyjs#3421)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
m-allanson authored and jastack committed Jan 24, 2018
1 parent 10cc9d2 commit 252b2ca
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 11 deletions.
28 changes: 18 additions & 10 deletions www/src/components/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "gatsby-link"
import GithubIcon from "react-icons/lib/go/mark-github"
import TwitterIcon from "react-icons/lib/fa/twitter"

import SearchForm from "../components/search-form"
import DiscordIcon from "../components/discord"
import logo from "../gatsby-negative.svg"
import typography, { rhythm, scale } from "../utils/typography"
Expand Down Expand Up @@ -117,30 +118,31 @@ export default ({ pathname }) => {
<Link
to="/"
css={{
alignItems: `center`,
color: `inherit`,
display: `inline-block`,
display: `flex`,
textDecoration: `none`,
marginRight: rhythm(0.5),
}}
>
<img
src={logo}
css={{
display: `inline-block`,
height: rhythm(1.2),
width: rhythm(1.2),
margin: 0,
marginRight: rhythm(2 / 4),
verticalAlign: `middle`,
}}
alt=""
/>
<h1
css={{
...scale(2 / 5),
display: `inline-block`,
margin: 0,
verticalAlign: `middle`,
display: `none`,
[presets.Mobile]: {
display: `block`,
},
}}
>
Gatsby
Expand All @@ -150,10 +152,15 @@ export default ({ pathname }) => {
css={{
display: `none`,
[presets.Tablet]: {
display: `block`,
display: `flex`,
margin: 0,
padding: 0,
listStyle: `none`,
flexGrow: 1,
overflowX: `auto`,
maskImage: `linear-gradient(to right, transparent, white ${rhythm(
1 / 8
)}, white 98%, transparent)`,
},
}}
>
Expand All @@ -165,12 +172,13 @@ export default ({ pathname }) => {
</ul>
<div
css={{
marginLeft: isHomepage ? rhythm(1 / 2) : `auto`,
[presets.Phablet]: {
marginLeft: isHomepage ? `auto` : `auto`,
},
display: `flex`,
marginLeft: `auto`,
}}
>
{!isHomepage && (
<SearchForm key="SearchForm" styles={{ ...navItemStyles }} />
)}
<a
href="https://github.com/gatsbyjs/gatsby"
title="GitHub"
Expand Down
231 changes: 231 additions & 0 deletions www/src/components/search-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { navigateTo } from "gatsby-link"
import { rhythm } from "../utils/typography"
import presets from "../utils/presets"

import { css } from "glamor"

// Override default search result styles
css.global(`.searchWrap .algolia-docsearch-suggestion--highlight`, {
backgroundColor: `${presets.lightPurple} !important`,
boxShadow: `inset 0 -2px 0 0 ${presets.lightPurple} !important`,
color: `black`,
fontWeight: `bold`,
})
css.global(`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column`, {
width: `100% !important`,
})
css.global(`.searchWrap .algolia-docsearch-suggestion--subcategory-column-text:after`, {
display: `none`,
})
css.global(
`.searchWrap .algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion:not(.suggestion-layout-simple) .algolia-docsearch-suggestion--content`,
{ backgroundColor: `${presets.brandLighter} !important` }
)
css.global(
`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content.algolia-docsearch-suggestion--no-results`,
{
maxWidth: `100%`,
paddingLeft: `0 !important`,
width: `100% !important`,
}
)
css.global(
`.searchWrap .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content.algolia-docsearch-suggestion--no-results:before`,
{ display: `none !important` }
)
css.global(`.searchWrap .algolia-autocomplete .ds-dropdown-menu`, {
position: `fixed !important`,
top: `${rhythm(2)} !important`,
left: `${rhythm(0.5)} !important`,
right: `${rhythm(0.5)} !important`,
minWidth: `calc(100vw - ${rhythm(1)})`,
maxWidth: `calc(100vw - 2rem)`,
maxHeight: `calc(100vh - 5rem)`,
display: `block`,
})
css.global(
`.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu, .algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu`,
{
left: `${rhythm(0.5)} !important`,
right: `${rhythm(0.5)} !important`,
}
)
css.global(
`.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu::before`,
{
right: rhythm(5),
}
)
css.global(
`.searchWrap .algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu::before`,
{
left: rhythm(7),
}
)

// use css.insert() for media query with global CSS
css.insert(`@media ${presets.phablet}{
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column {
font-weight: 400;
width: 30% !important;
text-align: right;
opacity: 1;
padding: ${rhythm(0.5)} ${rhythm(1)} ${rhythm(0.5)} 0;
}
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:before {
content: "";
position: absolute;
display: block !important;
top: 0;
height: 100%;
width: 1px;
background: #ddd;
right: 0;
}
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:after {
display: none;
}
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--content {
width: 70% !important;
max-width: 70%;
display: block;
padding: ${rhythm(0.5)} 0 ${rhythm(0.5)} ${rhythm(1)} !important;
}
.searchWrap .algolia-autocomplete .algolia-docsearch-suggestion--content:before {
content: "";
position: absolute;
display: block !important;
top: 0;
height: 100%;
width: 1px;
background: #ddd;
left: -1px;
}
}`)

css.insert(`@media ${presets.tablet}{
.searchWrap .algolia-autocomplete .ds-dropdown-menu {
top: 100% !important;
position: absolute !important;
max-width: 600px !important;
min-width: 500px !important;
}
.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu {
right: 0 !important;
left: inherit !important;
}
.searchWrap .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu::before {
right: ${rhythm(2)};
}
}`)

class SearchForm extends Component {
constructor() {
super()
this.state = { enabled: true }
}

/**
* Replace the default selection event, allowing us to do client-side
* navigation thus avoiding a full page refresh.
*
* Ref: https://github.com/algolia/autocomplete.js#events
*/
autocompleteSelected(e) {
e.stopPropagation()
// Use an anchor tag to parse the absolute url (from autocomplete.js) into a relative url
// eslint-disable-next-line no-undef
const a = document.createElement(`a`)
a.href = e._args[0].url
navigateTo(`${a.pathname}${a.hash}`)
}

componentDidMount() {
if (
typeof window === `undefined` || // eslint-disable-line no-undef
typeof window.docsearch === `undefined` // eslint-disable-line no-undef
) {
console.warn(`Search has failed to load and now is being disabled`)
this.setState({ enabled: false })
return
}

// eslint-disable-next-line no-undef
window.addEventListener(
`autocomplete:selected`,
this.autocompleteSelected,
true
)

// eslint-disable-next-line no-undef
window.docsearch({
apiKey: `71af1f9c4bd947f0252e17051df13f9c`,
indexName: `gatsbyjs`,
inputSelector: `#doc-search`,
debug: false,
})
}

render() {
const { enabled } = this.state
const { styles } = this.props.styles
return enabled ? (
<form
css={{
...styles,
display: `flex`,
flex: `0 0 auto`,
flexDirection: `row`,
alignItems: `center`,
marginLeft: rhythm(1 / 2),
marginBottom: 0,
}}
className="searchWrap"
>
<input
id="doc-search"
css={{
appearance: `none`,
background: `transparent`,
border: 0,
color: presets.brand,
paddingTop: rhythm(1 / 8),
paddingRight: rhythm(1 / 4),
paddingBottom: rhythm(1 / 8),
paddingLeft: rhythm(1),
backgroundImage: `url(/search.svg)`,
backgroundSize: `16px 16px`,
backgroundRepeat: `no-repeat`,
backgroundPositionY: `center`,
backgroundPositionX: `5px`,
overflow: `hidden`,
width: rhythm(1),
transition: `width 0.2s ease`,

":focus": {
outline: 0,
backgroundColor: presets.brandLighter,
borderRadius: presets.radiusLg,
width: rhythm(5),
},

[presets.Desktop]: {
width: rhythm(5),
},
}}
type="search"
placeholder="Search docs"
aria-label="Search docs"
/>
</form>
) : null
}
}

SearchForm.propTypes = {
styles: PropTypes.object,
}

export default SearchForm
1 change: 1 addition & 0 deletions www/src/components/sidebar-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class SidebarBody extends React.Component {
css={{
padding: isInline ? 0 : rhythm(3 / 4),
}}
className="docSearch-sidebar"
>
{menu.map((section, index) => (
<div
Expand Down
2 changes: 2 additions & 0 deletions www/src/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default class HTML extends React.Component {
href={`/safari-pinned-tab.svg`}
color="#5bbad5"
/>
<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
{css}
</head>
<body {...this.props.bodyAttributes}>
Expand All @@ -80,6 +81,7 @@ export default class HTML extends React.Component {
dangerouslySetInnerHTML={{ __html: this.props.body }}
/>
{this.props.postBodyComponents}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
</body>
</html>
)
Expand Down
3 changes: 2 additions & 1 deletion www/src/layouts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class DefaultLayout extends React.Component {
this.props.location.pathname.slice(0, 10) === `/packages/` ||
this.props.location.pathname.slice(0, 10) === `/tutorial/` ||
this.props.location.pathname.slice(0, 9) === `/features`
const isSearchSource = hasSidebar
const sidebarStyles = {
borderRight: `1px solid ${colors.b[0]}`,
backgroundColor: presets.sidebar,
Expand All @@ -45,7 +46,6 @@ class DefaultLayout extends React.Component {
position: `fixed`,
top: `calc(${presets.headerHeight} - 1px)`,
overflowY: `auto`,
zIndex: 1,
height: `calc(100vh - ${presets.headerHeight} + 1px)`,
WebkitOverflowScrolling: `touch`,
"::-webkit-scrollbar": {
Expand Down Expand Up @@ -136,6 +136,7 @@ class DefaultLayout extends React.Component {
paddingLeft: hasSidebar ? rhythm(12) : 0,
},
}}
className={isSearchSource && `docSearch-content`}
>
{this.props.children()}
</div>
Expand Down
4 changes: 4 additions & 0 deletions www/static/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 252b2ca

Please sign in to comment.