Browse Source

Fix search modal not disappearing on Enter (#5794)

* Remove dead prop

* Use React.lazy

* Render at most one dialog at a time
main
dan 2 years ago
committed by GitHub
parent
commit
8de8a0342d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 62
      src/components/Layout/TopNav/TopNav.tsx
  2. 102
      src/components/Search.tsx

62
src/components/Layout/TopNav/TopNav.tsx

@ -2,7 +2,14 @@
* Copyright (c) Facebook, Inc. and its affiliates. * Copyright (c) Facebook, Inc. and its affiliates.
*/ */
import {useState, useRef, useEffect, Suspense} from 'react'; import {
useState,
useRef,
useCallback,
useEffect,
startTransition,
Suspense,
} from 'react';
import * as React from 'react'; import * as React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import NextLink from 'next/link'; import NextLink from 'next/link';
@ -11,6 +18,7 @@ import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock';
import {IconClose} from 'components/Icon/IconClose'; import {IconClose} from 'components/Icon/IconClose';
import {IconHamburger} from 'components/Icon/IconHamburger'; import {IconHamburger} from 'components/Icon/IconHamburger';
import {IconSearch} from 'components/Icon/IconSearch';
import {Search} from 'components/Search'; import {Search} from 'components/Search';
import {Logo} from '../../Logo'; import {Logo} from '../../Logo';
import {Feedback} from '../Feedback'; import {Feedback} from '../Feedback';
@ -112,6 +120,18 @@ function NavItem({url, isActive, children}: any) {
); );
} }
function Kbd(props: {children?: React.ReactNode; wide?: boolean}) {
const {wide, ...rest} = props;
const width = wide ? 'w-10' : 'w-5';
return (
<kbd
className={`${width} h-5 border border-transparent mr-1 bg-wash dark:bg-wash-dark text-gray-30 align-middle p-0 inline-flex justify-center items-center text-xs text-center rounded-md`}
{...rest}
/>
);
}
export default function TopNav({ export default function TopNav({
routeTree, routeTree,
breadcrumbs, breadcrumbs,
@ -183,8 +203,23 @@ export default function TopNav({
return () => observer.disconnect(); return () => observer.disconnect();
}, []); }, []);
const [showSearch, setShowSearch] = useState(false);
const onOpenSearch = useCallback(() => {
startTransition(() => {
setShowSearch(true);
});
}, []);
const onCloseSearch = useCallback(() => {
setShowSearch(false);
}, []);
return ( return (
<> <>
<Search
isOpen={showSearch}
onOpen={onOpenSearch}
onClose={onCloseSearch}
/>
<div ref={scrollDetectorRef} /> <div ref={scrollDetectorRef} />
<div <div
className={cn( className={cn(
@ -226,7 +261,22 @@ export default function TopNav({
</div> </div>
</div> </div>
<div className="hidden md:flex flex-1 justify-center items-center w-full 3xl:w-auto 3xl:shrink-0 3xl:justify-center"> <div className="hidden md:flex flex-1 justify-center items-center w-full 3xl:w-auto 3xl:shrink-0 3xl:justify-center">
<Search /> <button
type="button"
className={cn(
'flex 3xl:w-[56rem] 3xl:mx-0 relative pl-4 pr-1 py-1 h-10 bg-gray-30/20 dark:bg-gray-40/20 outline-none focus:outline-link betterhover:hover:bg-opacity-80 pointer items-center text-left w-full text-gray-30 rounded-full align-middle text-base'
)}
onClick={onOpenSearch}>
<IconSearch className="mr-3 align-middle text-gray-30 shrink-0 group-betterhover:hover:text-gray-70" />
Search
<span className="ml-auto hidden sm:flex item-center mr-1">
<Kbd data-platform="mac"></Kbd>
<Kbd data-platform="win" wide>
Ctrl
</Kbd>
<Kbd>K</Kbd>
</span>
</button>
</div> </div>
<div className="text-base justify-center items-center gap-1.5 flex 3xl:flex-1 flex-row 3xl:justify-end"> <div className="text-base justify-center items-center gap-1.5 flex 3xl:flex-1 flex-row 3xl:justify-end">
<div className="mx-2.5 gap-1.5 hidden lg:flex"> <div className="mx-2.5 gap-1.5 hidden lg:flex">
@ -248,7 +298,13 @@ export default function TopNav({
<div className="flex w-full md:hidden"></div> <div className="flex w-full md:hidden"></div>
<div className="flex items-center -space-x-2.5 xs:space-x-0 "> <div className="flex items-center -space-x-2.5 xs:space-x-0 ">
<div className="flex md:hidden"> <div className="flex md:hidden">
<Search /> <button
aria-label="Search"
type="button"
className="active:scale-95 transition-transform flex md:hidden w-12 h-12 rounded-full items-center justify-center hover:bg-secondary-button hover:dark:bg-secondary-button-dark outline-link"
onClick={onOpenSearch}>
<IconSearch className="align-middle w-5 h-5" />
</button>
</div> </div>
<div className="flex dark:hidden"> <div className="flex dark:hidden">
<button <button

102
src/components/Search.tsx

@ -2,12 +2,10 @@
* Copyright (c) Facebook, Inc. and its affiliates. * Copyright (c) Facebook, Inc. and its affiliates.
*/ */
// @ts-ignore
import {IconSearch} from 'components/Icon/IconSearch';
import Head from 'next/head'; import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
import Router from 'next/router'; import Router from 'next/router';
import {useState, useCallback, useEffect} from 'react'; import {lazy, useCallback, useEffect} from 'react';
import * as React from 'react'; import * as React from 'react';
import {createPortal} from 'react-dom'; import {createPortal} from 'react-dom';
import {siteConfig} from 'siteConfig'; import {siteConfig} from 'siteConfig';
@ -18,8 +16,9 @@ export interface SearchProps {
apiKey?: string; apiKey?: string;
indexName?: string; indexName?: string;
searchParameters?: any; searchParameters?: any;
renderModal?: boolean; isOpen: boolean;
fullsize?: boolean; onOpen: () => void;
onClose: () => void;
} }
function Hit({hit, children}: any) { function Hit({hit, children}: any) {
@ -30,18 +29,6 @@ function Hit({hit, children}: any) {
); );
} }
function Kbd(props: {children?: React.ReactNode; wide?: boolean}) {
const {wide, ...rest} = props;
const width = wide ? 'w-10' : 'w-5';
return (
<kbd
className={`${width} h-5 border border-transparent mr-1 bg-wash dark:bg-wash-dark text-gray-30 align-middle p-0 inline-flex justify-center items-center text-xs text-center rounded-md`}
{...rest}
/>
);
}
// Copy-pasted from @docsearch/react to avoid importing the whole bundle. // Copy-pasted from @docsearch/react to avoid importing the whole bundle.
// Slightly trimmed to features we use. // Slightly trimmed to features we use.
// (c) Algolia, Inc. // (c) Algolia, Inc.
@ -99,49 +86,23 @@ const options = {
apiKey: siteConfig.algolia.apiKey, apiKey: siteConfig.algolia.apiKey,
indexName: siteConfig.algolia.indexName, indexName: siteConfig.algolia.indexName,
}; };
let DocSearchModal: any = null;
export function Search({
searchParameters = {
hitsPerPage: 5,
},
fullsize,
}: SearchProps) {
const [isShowing, setIsShowing] = useState(false);
const importDocSearchModalIfNeeded = useCallback(
function importDocSearchModalIfNeeded() {
if (DocSearchModal) {
return Promise.resolve();
}
const DocSearchModal: any = lazy(() =>
// @ts-ignore // @ts-ignore
return import('@docsearch/react/modal').then( import('@docsearch/react/modal').then((mod) => ({
({DocSearchModal: Modal}) => { default: mod.DocSearchModal,
DocSearchModal = Modal; }))
}
);
},
[]
); );
const onOpen = useCallback( export function Search({
function onOpen() { isOpen,
importDocSearchModalIfNeeded().then(() => { onOpen,
setIsShowing(true); onClose,
}); searchParameters = {
}, hitsPerPage: 5,
[importDocSearchModalIfNeeded, setIsShowing]
);
const onClose = useCallback(
function onClose() {
setIsShowing(false);
}, },
[setIsShowing] }: SearchProps) {
); useDocSearchKeyboardEvents({isOpen, onOpen, onClose});
useDocSearchKeyboardEvents({isOpen: isShowing, onOpen, onClose});
return ( return (
<> <>
<Head> <Head>
@ -150,36 +111,7 @@ export function Search({
href={`https://${options.appId}-dsn.algolia.net`} href={`https://${options.appId}-dsn.algolia.net`}
/> />
</Head> </Head>
{isOpen &&
{!fullsize && (
<button
aria-label="Search"
type="button"
className="active:scale-95 transition-transform flex md:hidden w-12 h-12 rounded-full items-center justify-center hover:bg-secondary-button hover:dark:bg-secondary-button-dark outline-link"
onClick={onOpen}>
<IconSearch className="align-middle w-5 h-5" />
</button>
)}
<button
type="button"
className={cn(
'3xl:w-[56rem] 3xl:mx-0 relative pl-4 pr-1 py-1 h-10 bg-gray-30/20 dark:bg-gray-40/20 outline-none focus:outline-link betterhover:hover:bg-opacity-80 pointer items-center text-left w-full text-gray-30 rounded-full align-middle text-base',
fullsize ? 'flex' : 'hidden md:flex'
)}
onClick={onOpen}>
<IconSearch className="mr-3 align-middle text-gray-30 shrink-0 group-betterhover:hover:text-gray-70" />
Search
<span className="ml-auto hidden sm:flex item-center mr-1">
<Kbd data-platform="mac"></Kbd>
<Kbd data-platform="win" wide>
Ctrl
</Kbd>
<Kbd>K</Kbd>
</span>
</button>
{isShowing &&
createPortal( createPortal(
<DocSearchModal <DocSearchModal
{...options} {...options}

Loading…
Cancel
Save