Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding doc search #9

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This Next.js starter is powered by TinaCMS and based upon [Smooth Doc](https://g
- MDX component support for easy to use resuable components
- Vercel deployment to visually edit your site from the /admin route.
- Local development workflow from the filesystem with a local GraqhQL server.
- Docs powered by https://docsearch.algolia.com/


## Requirements
Expand Down Expand Up @@ -41,6 +42,19 @@ yarn dev
- http://localhost:3000/exit-admin : log out of Tina Cloud
- http://localhost:4001/altair/ : GraphQL playground to test queries and browse the API documentation


## Wanting to use Doc search?

The first step is to apply for [Algolia DocSearch](https://docsearch.algolia.com/apply/). Once that is done you can update the `/config/siteMetadata.js` with the apiKey and Index.

```json
docSearch: {
apiKey: 'API_KEY',
indexName: 'INDEX_NAME',
},
```
Once you do that, search will appear in the header.

## Getting Help

TinaCMS backend is in public beta, you might face issues, to provide feedback or get help with any challenges you may have:
Expand Down
10 changes: 6 additions & 4 deletions components/AppHeader.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from 'react'

import styled, { x, down, css } from '@xstyled/styled-components'
import { ScreenContainer } from './ScreenContainer'
import { NavLink } from './Nav'
import { AppNav } from './AppNav'

import { DocSearch } from './DocSearch'
import siteMetadata from '../config/siteMetadata'


const OuterHeader = styled.header`
background-color: background;
border-bottom-style: solid;
Expand Down Expand Up @@ -91,6 +88,11 @@ export function AppHeader() {
)}
</NavLink>
</x.div>
{siteMetadata.docSearch.apiKey.length >1 ? (
<x.div col="auto" px={2} display={{ xs: 'none', md: 'block' }}>
<DocSearch {...siteMetadata.docSearch} />
</x.div>
) : null}
<AppNav col="auto" px={2} />
</x.div>
</ScreenContainer>
Expand Down
169 changes: 169 additions & 0 deletions components/DocSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import * as React from 'react'
import { useRouter } from 'next/router'
import { createPortal } from 'react-dom'
import styled, { x, createGlobalStyle } from '@xstyled/styled-components'
import { useDocSearchKeyboardEvents } from '@docsearch/react'
import { RiSearchLine } from 'react-icons/ri'
import { Input, InputGroup, InputGroupIcon } from './Input'

require('@docsearch/css')

const GlobalStyle = createGlobalStyle`
/* Darkmode */
body.xstyled-color-mode-dark {
--docsearch-text-color: rgb(245, 246, 247);
--docsearch-container-background: rgba(9, 10, 17, 0.8);
--docsearch-modal-background: rgb(21, 23, 42);
--docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64),
0 3px 8px 0 rgb(0, 3, 9);
--docsearch-searchbox-background: rgb(9, 10, 17);
--docsearch-searchbox-focus-background: #000;
--docsearch-hit-color: rgb(190, 195, 201);
--docsearch-hit-shadow: none;
--docsearch-hit-background: rgb(9, 10, 17);
--docsearch-key-gradient: linear-gradient(
-26.5deg,
rgb(86, 88, 114) 0%,
rgb(49, 53, 91) 100%
);
--docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85),
inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3);
--docsearch-footer-background: rgb(30, 33, 54);
--docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),
0 -4px 8px 0 rgba(0, 0, 0, 0.2);
--docsearch-logo-color: rgb(255, 255, 255);
--docsearch-muted-color: rgb(127, 132, 151);
}
`

function Hit({ hit, children }) {
return (
<a href={hit.url}>{children}</a>
)
}

const Kbd = styled.kbd`
border: 1;
border-color: control-border;
margin-right: 1;
background-color: control-background;
text-align: center;
padding: 0;
display: inline-flex;
justify-content: center;
font-size: 0.8em;
line-height: 1.2;
font-family: sans-serif;
border-radius: base;
min-width: 1.5em;
`

let DocSearchModal = null

export const DocSearch = ({ apiKey, indexName }) => {
const searchButtonRef = React.useRef(null)
const [isShowing, setIsShowing] = React.useState(false)
const [initialQuery, setInitialQuery] = React.useState(null)
const router = useRouter()

const importDocSearchModalIfNeeded = React.useCallback(() => {
if (DocSearchModal) {
return Promise.resolve()
}

return Promise.resolve(import('@docsearch/react/modal')).then(
({ DocSearchModal: Modal }) => {
DocSearchModal = Modal
},
)
}, [])

const onOpen = React.useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
if (document.body.classList.contains('DocSearch--active')) {
return
}

setIsShowing(true)
})
}, [importDocSearchModalIfNeeded, setIsShowing])

const onClose = React.useCallback(() => {
setIsShowing(false)
}, [setIsShowing])

const onInput = React.useCallback(
(event) => {
importDocSearchModalIfNeeded().then(() => {
setIsShowing(true)
setInitialQuery(event.key)
})
},
[importDocSearchModalIfNeeded, setIsShowing, setInitialQuery],
)

useDocSearchKeyboardEvents({
isOpen: isShowing,
onOpen,
onClose,
onInput,
searchButtonRef,
})

return (
<>
<GlobalStyle />
<div>
<InputGroup>
<InputGroupIcon>
<RiSearchLine />
</InputGroupIcon>
<Input
ref={searchButtonRef}
onClick={onOpen}
type="search"
placeholder="Search..."
/>
<x.div
position="absolute"
top="50%"
right={0}
transform="translateY(-50%)"
display="inline-flex"
pointerEvents="none"
userSelect="none"
>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</x.div>
</InputGroup>
</div>

{isShowing &&
createPortal(
<DocSearchModal
apiKey={apiKey}
indexName={indexName}
initialQuery={initialQuery}
onClose={onClose}
navigator={{
navigate({ suggestionUrl }) {
router.push(suggestionUrl)
},
}}
transformItems={(items) => {
return items.map((item) => {
const url = new URL(item.url)
return {
...item,
url: item.url.replace(url.origin, ''),
}
})
}}
hitComponent={Hit}
/>,
document.body,
)}
</>
)
}
4 changes: 4 additions & 0 deletions config/siteMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const siteMetadata = {
socialImage: "/images/social.jpg",
navItems: [{ title: "Docs", url: "/docs/" }],
twitterAccount: "tina_cms",
docSearch: {
apiKey: '',
indexName: '',
},
siteUrl: "https://tina.io",
author: "TinaCMS",
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"start": "yarn tinacms server:start -c \"next start\""
},
"dependencies": {
"@docsearch/react": "^3.0.0-alpha.41",
"@tinacms/auth": "^0.50.1",
"@tinacms/cli": "^0.56.4",
"@xstyled/styled-components": "^3.0.0",
Expand Down
Loading