diff --git a/gatsby-ssr.js b/gatsby-ssr.js index 99a9c5d95..1bd8a7722 100644 --- a/gatsby-ssr.js +++ b/gatsby-ssr.js @@ -1,6 +1,7 @@ /*eslint-env node */ const React = require('react') +const { withPrefix } = require('gatsby') // Adds the dark mode class name to the html element before render, // using an inline script to void a flash of white in dark mode @@ -14,6 +15,9 @@ exports.onRenderBody = ({ setHeadComponents }) => { // Add the new scheme document.documentElement.classList.add(scheme) + + // Pagefind's search UI reads this attribute to theme itself + document.documentElement.setAttribute('data-pf-theme', scheme) } var isDarkMode = @@ -38,5 +42,18 @@ exports.onRenderBody = ({ setHeadComponents }) => { key='dark-mode-script' dangerouslySetInnerHTML={{ __html: script }} >, + // Pagefind's search index and UI bundle are generated as a post-build + // step (see the `build` script), so they're loaded from the static + // output directory rather than bundled as an npm dependency. + , + , ]) } diff --git a/package.json b/package.json index 451f59e12..f9ce1c6d8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "dependencies": { "@babel/core": "^7.15.0", "@babel/eslint-parser": "^7.15.0", - "@docsearch/react": "^3.5.2", "@josephuspaye/gatsby-remark-graphviz": "^0.3.1", "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", @@ -37,6 +36,7 @@ "gatsby-transformer-sharp": "^3.11.0", "husky": "^7.0.0", "katex": "^0.13.13", + "pagefind": "^1.5.2", "postcss": "^8.3.6", "postcss-nested": "^5.0.6", "prettier": "^2.3.2", @@ -58,7 +58,7 @@ }, "license": "MIT", "scripts": { - "build": "gatsby build", + "build": "gatsby build && pagefind --site public", "dev": "gatsby develop", "clean": "rimraf .cache/ public/", "lint": "eslint \"src/**/*.{jsx,js}\" \"*.js\" && stylelint \"src/**/*.css\"", diff --git a/src/components/header/color-scheme-toggle/color-scheme-toggle.jsx b/src/components/header/color-scheme-toggle/color-scheme-toggle.jsx index 73c2eb031..dff58673d 100644 --- a/src/components/header/color-scheme-toggle/color-scheme-toggle.jsx +++ b/src/components/header/color-scheme-toggle/color-scheme-toggle.jsx @@ -9,6 +9,9 @@ function setColorScheme(scheme) { scheme === 'light' ? 'dark' : 'light' ) document.documentElement.classList.add(scheme) + + // Pagefind's search UI reads this attribute to theme itself + document.documentElement.setAttribute('data-pf-theme', scheme) } function toggleColorScheme() { diff --git a/src/components/header/search/search.jsx b/src/components/header/search/search.jsx index 087d5a043..6ebf06dbe 100644 --- a/src/components/header/search/search.jsx +++ b/src/components/header/search/search.jsx @@ -1,40 +1,41 @@ -import React from 'react' -import PropTypes from 'prop-types' -import '@docsearch/css' +import React, { useEffect, useRef } from 'react' import './search.module.css' -import { DocSearch } from '@docsearch/react' const Search = () => { - return ( - //
- { - return items.map((item) => { - // Get the origin (domain + port, if any) from the result URL - const { origin } = new URL(item.url) - return { - ...item, - // Replace the origin in the result URL with our current origin, - // so the links work for local development and deploy previews - url: item.url.replace(origin, window.location.origin), - } - }) - }} - /> - //
- ) -} + const triggerRef = useRef(null) + const modalRef = useRef(null) -Search.propTypes = { - background: PropTypes.string, -} + // Pagefind's web components mutate their own children as soon as they're + // upgraded (e.g. pagefind-modal adds an internal ). If React + // renders them via JSX, that mutation happens before/during hydration and + // React "corrects" it by tearing the mutated children back out, breaking + // the modal. So they're mounted imperatively into empty wrapper divs that + // React never diffs into, after hydration has already settled. + useEffect(() => { + const triggerEl = triggerRef.current + const modalEl = modalRef.current + + const trigger = document.createElement('pagefind-modal-trigger') + trigger.setAttribute('placeholder', 'Search NUbook...') + trigger.setAttribute('shortcut', 'mod+k') + triggerEl.appendChild(trigger) -Search.defaultProps = { - background: 'solid', + const modal = document.createElement('pagefind-modal') + modal.setAttribute('reset-on-close', '') + modalEl.appendChild(modal) + + return () => { + triggerEl.removeChild(trigger) + modalEl.removeChild(modal) + } + }, []) + + return ( + <> +
+
+ + ) } export default Search diff --git a/src/components/header/search/search.module.css b/src/components/header/search/search.module.css index cafafab2e..e5a811b30 100644 --- a/src/components/header/search/search.module.css +++ b/src/components/header/search/search.module.css @@ -1,120 +1,16 @@ -*:global.DocSearch-Button { - backdrop-filter: blur(4px); - font-weight: inherit; - transition: all 0.2s ease-in-out; - - @apply m-0 h-10 w-full pr-3 md:px-4 rounded appearance-none outline-none; - - &:hover { - box-shadow: none; - } - - &:focus { - box-shadow: none; - } -} - -*:global.DocSearch-Button-Keys { - display: none; -} - -*:global.DocSearch-Button-Container { - @apply gap-3 w-full; -} - -*:global.DocSearch-Button-Placeholder { +/* The search bar always sits on the header's dark bg (bg-gray-900), + regardless of the site's light/dark theme (set via data-pf-theme on + , see gatsby-ssr.js and color-scheme-toggle.jsx), so the trigger + button's own styling stays constant. An element's own declarations + always win over an inherited value, so this overrides whatever + data-pf-theme cascades down regardless of the current theme. */ +pagefind-modal-trigger { display: block; + width: 100%; - @apply text-lg; -} - -*:global.DocSearch-Modal { - box-shadow: none; -} - -*:global.DocSearch-Hits { - mark { - @apply font-semibold; - } -} - -*:global.DocSearch-Footer { - box-shadow: none; - - *:global.DocSearch-Commands { - display: none; - } -} - -*:global.DocSearch-Form { - box-shadow: none; -} - -:global(.dark) { - --docsearch-container-background: rgba(115, 115, 115, 0.8); - --docsearch-modal-background: gray-700; - --docsearch-footer-background: gray-700; - --docsearch-hit-color: text-primary-inverted; - --docsearch-text-color: text-primary-inverted; - --docsearch-hit-background: rgba(255, 255, 255, 0.07); - --docsearch-hit-active-color: #000; - --docsearch-highlight-color: #f9a50d; - --docsearch-hit-shadow: none; - - *:global.DocSearch-Button { - background-color: rgba(255, 255, 255, 0.15); - color: hsla(0, 0%, 100%, 0.45); - - > :global.DocSearch-Button-Container { - > :global.DocSearch-Search-Icon { - color: hsla(0, 0%, 100%, 0.45); - } - } - - &:hover { - box-shadow: none; - background-color: rgba(66, 66, 66, 1); - } - } - - *:global.DocSearch-Modal { - background-color: #222; - } - - *:global.DocSearch-Form { - background-color: rgba(255, 255, 255, 0.075); - } - - *:global.DocSearch-Hit-source { - background-color: #222; - } -} - -:global(.light) { - --docsearch-container-background: rgba(115, 115, 115, 0.8); - --docsearch-highlight-color: #cc880d; - --docsearch-hit-active-color: #fff; - - *:global.DocSearch-Button { - background-color: rgba(255, 255, 255, 0.15); - color: hsla(0, 0%, 100%, 0.45); - - > :global.DocSearch-Button-Container { - > :global.DocSearch-Search-Icon { - color: hsla(0, 0%, 100%, 0.45); - } - } - - &:hover { - box-shadow: none; - background-color: rgba(255, 255, 255, 1); - color: rgba(50, 50, 50, 0.87); - - > :global.DocSearch-Button-Container { - > :global.DocSearch-Search-Icon { - color: rgba(50, 50, 50, 0.87); - } - } - } - } + --pf-background: rgba(255, 255, 255, 0.15); + --pf-text: rgba(255, 255, 255, 0.87); + --pf-text-secondary: hsla(0, 0%, 100%, 0.45); + --pf-border: transparent; + --pf-border-radius: 0.25rem; } diff --git a/src/components/layout.jsx b/src/components/layout.jsx index c62df470a..88831c097 100644 --- a/src/components/layout.jsx +++ b/src/components/layout.jsx @@ -72,7 +72,7 @@ const Layout = ({ children, data, pageContext, contributions }) => { )}
-
+