-
-
Notifications
You must be signed in to change notification settings - Fork 694
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
fix: address search bar issue in the navbar #3444
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,22 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
import { DocSearchModal } from '@docsearch/react'; | ||
import type { DocSearchHit, InternalDocSearchHit, StoredDocSearchHit } from '@docsearch/react/dist/esm/types'; | ||
import type { | ||
DocSearchHit, | ||
InternalDocSearchHit, | ||
StoredDocSearchHit, | ||
} from '@docsearch/react/dist/esm/types'; | ||
import clsx from 'clsx'; | ||
import Head from 'next/head'; | ||
import Link from 'next/link'; | ||
import { useRouter } from 'next/router'; | ||
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; | ||
import React, { | ||
createContext, | ||
useCallback, | ||
useContext, | ||
useEffect, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
import { createPortal } from 'react-dom'; | ||
|
||
export const INDEX_NAME = 'asyncapi'; | ||
|
@@ -46,8 +57,17 @@ interface IUseDocSearchKeyboardEvents { | |
onInput?: (e: React.KeyboardEvent) => void; | ||
} | ||
|
||
type ISearchButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> & { | ||
children?: React.ReactNode | (({ actionKey }: { actionKey: { shortKey: string; key: string } }) => React.ReactNode); | ||
type ISearchButtonProps = Omit< | ||
React.ButtonHTMLAttributes<HTMLButtonElement>, | ||
'children' | ||
> & { | ||
children?: | ||
| React.ReactNode | ||
| (({ | ||
actionKey, | ||
}: { | ||
actionKey: { shortKey: string; key: string }; | ||
}) => React.ReactNode); | ||
indexName?: string; | ||
}; | ||
|
||
|
@@ -64,7 +84,8 @@ function transformItems(items: DocSearchHit[]) { | |
|
||
a.href = item.url; | ||
|
||
const hash = a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash; | ||
const hash = | ||
a.hash === '#content-wrapper' || a.hash === '#header' ? '' : a.hash; | ||
|
||
if (item.hierarchy?.lvl0) { | ||
// eslint-disable-next-line no-param-reassign | ||
|
@@ -75,10 +96,15 @@ function transformItems(items: DocSearchHit[]) { | |
...item, | ||
url: `${a.pathname}${hash}`, | ||
__is_result: () => true, | ||
__is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0, | ||
__is_child: () => item.type !== 'lvl1' && items.length > 1 && items[0].type === 'lvl1' && index !== 0, | ||
__is_parent: () => | ||
item.type === 'lvl1' && items.length > 1 && index === 0, | ||
__is_child: () => | ||
item.type !== 'lvl1' && | ||
items.length > 1 && | ||
items[0].type === 'lvl1' && | ||
index !== 0, | ||
__is_first: () => index === 1, | ||
__is_last: () => index === items.length - 1 && index !== 0 | ||
__is_last: () => index === items.length - 1 && index !== 0, | ||
}; | ||
}); | ||
} | ||
|
@@ -97,7 +123,7 @@ function Hit({ hit, children }: IHitProps) { | |
'DocSearch-Hit--Parent': hit.__is_parent?.(), | ||
'DocSearch-Hit--FirstChild': hit.__is_first?.(), | ||
'DocSearch-Hit--LastChild': hit.__is_last?.(), | ||
'DocSearch-Hit--Child': hit.__is_child?.() | ||
'DocSearch-Hit--Child': hit.__is_child?.(), | ||
})} | ||
> | ||
{children} | ||
|
@@ -118,9 +144,13 @@ function AlgoliaModal({ onClose, initialQuery, indexName }: AlgoliaModalProps) { | |
initialQuery={initialQuery} | ||
initialScrollY={window.scrollY} | ||
searchParameters={{ | ||
distinct: 1 | ||
distinct: 1, | ||
}} | ||
placeholder={indexName === DOCS_INDEX_NAME ? 'Search documentation' : 'Search resources'} | ||
placeholder={ | ||
indexName === DOCS_INDEX_NAME | ||
? 'Search documentation' | ||
: 'Search resources' | ||
} | ||
onClose={onClose} | ||
indexName={indexName} | ||
apiKey={API_KEY} | ||
|
@@ -129,15 +159,15 @@ function AlgoliaModal({ onClose, initialQuery, indexName }: AlgoliaModalProps) { | |
navigate({ itemUrl }) { | ||
onClose(); | ||
router.push(itemUrl); | ||
} | ||
}, | ||
}} | ||
hitComponent={Hit} | ||
transformItems={transformItems} | ||
getMissingResultsUrl={({ query }) => { | ||
return `https://github.com/asyncapi/website/issues/new?title=Cannot%20search%20given%20query:%20${query}`; | ||
}} | ||
/>, | ||
document.body | ||
document.body, | ||
); | ||
} | ||
|
||
|
@@ -151,7 +181,10 @@ function isEditingContent(event: KeyboardEvent) { | |
const { tagName } = element as HTMLElement; | ||
|
||
return ( | ||
(element as HTMLElement).isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' | ||
(element as HTMLElement).isContentEditable || | ||
tagName === 'INPUT' || | ||
tagName === 'SELECT' || | ||
tagName === 'TEXTAREA' | ||
); | ||
} | ||
|
||
|
@@ -161,22 +194,24 @@ function isEditingContent(event: KeyboardEvent) { | |
*/ | ||
function getActionKey() { | ||
if (typeof navigator !== 'undefined') { | ||
if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent || navigator.platform)) { | ||
if ( | ||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent || navigator.platform) | ||
) { | ||
return { | ||
shortKey: '⌘', | ||
key: 'Command' | ||
key: 'Command', | ||
}; | ||
} | ||
|
||
return { | ||
shortKey: 'Ctrl', | ||
key: 'Control' | ||
key: 'Control', | ||
}; | ||
} | ||
|
||
return { | ||
shortKey: 'Ctrl', | ||
key: 'Control' | ||
key: 'Control', | ||
}; | ||
} | ||
|
||
|
@@ -185,7 +220,11 @@ function getActionKey() { | |
* @description The hook used for the Algolia search keyboard events | ||
* @param {IUseDocSearchKeyboardEvents} props - The props of the useDocSearchKeyboardEvents hook | ||
*/ | ||
function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKeyboardEvents) { | ||
function useDocSearchKeyboardEvents({ | ||
isOpen, | ||
onOpen, | ||
onClose, | ||
}: IUseDocSearchKeyboardEvents) { | ||
useEffect(() => { | ||
/** | ||
* @description The function used to handle the keyboard event. | ||
|
@@ -211,7 +250,9 @@ function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKe | |
if (typeof document !== 'undefined') { | ||
const loc = document.location; | ||
|
||
indexName = loc.pathname.startsWith('/docs') ? DOCS_INDEX_NAME : INDEX_NAME; | ||
indexName = loc.pathname.startsWith('/docs') | ||
? DOCS_INDEX_NAME | ||
: INDEX_NAME; | ||
} | ||
onOpen(indexName); | ||
} | ||
|
@@ -231,7 +272,11 @@ function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }: IUseDocSearchKe | |
* @description The Algolia search component used for searching the website | ||
* @param {React.ReactNode} children - The content of the page | ||
*/ | ||
export default function AlgoliaSearch({ children }: { children: React.ReactNode }) { | ||
export default function AlgoliaSearch({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
const [isOpen, setIsOpen] = useState(false); | ||
const [indexName, setIndexName] = useState<string>(INDEX_NAME); | ||
const [initialQuery, setInitialQuery] = useState<string>(); | ||
|
@@ -243,7 +288,7 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode | |
} | ||
setIsOpen(true); | ||
}, | ||
[setIsOpen, setIndexName] | ||
[setIsOpen, setIndexName], | ||
); | ||
|
||
const onClose = useCallback(() => { | ||
|
@@ -255,23 +300,35 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode | |
setIsOpen(true); | ||
setInitialQuery(e.key); | ||
}, | ||
[setIsOpen, setInitialQuery] | ||
[setIsOpen, setInitialQuery], | ||
); | ||
|
||
useDocSearchKeyboardEvents({ | ||
isOpen, | ||
onOpen, | ||
onClose, | ||
onInput | ||
onInput, | ||
}); | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<link rel='preconnect' href={`https://${APP_ID}-dsn.algolia.net`} crossOrigin='anonymous' /> | ||
<link | ||
rel="preconnect" | ||
href={`https://${APP_ID}-dsn.algolia.net`} | ||
crossOrigin="anonymous" | ||
/> | ||
</Head> | ||
<SearchContext.Provider value={{ isOpen, onOpen, onClose, onInput }}>{children}</SearchContext.Provider> | ||
{isOpen && <AlgoliaModal initialQuery={initialQuery ?? ''} onClose={onClose} indexName={indexName} />} | ||
<SearchContext.Provider value={{ isOpen, onOpen, onClose, onInput }}> | ||
{children} | ||
</SearchContext.Provider> | ||
{isOpen && ( | ||
<AlgoliaModal | ||
initialQuery={initialQuery ?? ''} | ||
onClose={onClose} | ||
indexName={indexName} | ||
/> | ||
)} | ||
</> | ||
); | ||
} | ||
|
@@ -281,7 +338,11 @@ export default function AlgoliaSearch({ children }: { children: React.ReactNode | |
* @description The search button component used for opening the Algolia search | ||
* @param {ISearchButtonProps} props - The props of the search button | ||
*/ | ||
export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISearchButtonProps) { | ||
export function SearchButton({ | ||
children, | ||
indexName = INDEX_NAME, | ||
...props | ||
}: ISearchButtonProps) { | ||
const { onOpen, onInput } = useContext(SearchContext); | ||
const [Children, setChildren] = useState<string | React.ReactNode>(''); | ||
const searchButtonRef = useRef<HTMLButtonElement>(null); | ||
|
@@ -295,7 +356,11 @@ export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISe | |
* @returns {void} | ||
*/ | ||
function onKeyDown(event: KeyboardEvent) { | ||
if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) { | ||
if ( | ||
searchButtonRef && | ||
searchButtonRef.current === document.activeElement && | ||
onInput | ||
) { | ||
if (/[a-zA-Z0-9]/.test(event.key)) { | ||
onInput(event as unknown as React.KeyboardEvent); | ||
} | ||
|
@@ -312,18 +377,20 @@ export function SearchButton({ children, indexName = INDEX_NAME, ...props }: ISe | |
useEffect(() => { | ||
if (typeof children === 'function') { | ||
setChildren(children({ actionKey })); | ||
} else { | ||
setChildren(children); | ||
} | ||
}, []); | ||
|
||
return ( | ||
<button | ||
type='button' | ||
type="button" | ||
ref={searchButtonRef} | ||
onClick={() => { | ||
onOpen(indexName); | ||
}} | ||
{...props} | ||
data-testid='Search-Button' | ||
data-testid="Search-Button" | ||
> | ||
{Children} | ||
</button> | ||
Comment on lines
+387
to
396
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add IconLoupe to button content To address the visibility issue, ensure the IconLoupe is rendered within the button when no children are provided. <button
type="button"
ref={searchButtonRef}
onClick={() => {
onOpen(indexName);
}}
{...props}
data-testid="Search-Button"
>
- {Children}
+ {Children || <IconLoupe className="h-5 w-5" />}
</button>
🧰 Tools🪛 eslint[error] 387-387: Replace (prettier/prettier) [error] 387-387: Unexpected usage of doublequote. (jsx-quotes) [error] 393-393: Replace (prettier/prettier) [error] 393-393: Unexpected usage of doublequote. (jsx-quotes) |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing IconLoupe import
The PR objective mentions fixing the visibility of IconLoupe component, but the import statement for this component is missing. This could be the root cause of the search icon not being visible.
Add the following import:
import { DocSearchModal } from '@docsearch/react'; + import { IconLoupe } from '@/components/icons';
📝 Committable suggestion
🧰 Tools
🪛 eslint
[error] 3-7: Replace
⏎··DocSearchHit,⏎··InternalDocSearchHit,⏎··StoredDocSearchHit,⏎
with·DocSearchHit,·InternalDocSearchHit,·StoredDocSearchHit·
(prettier/prettier)
[error] 6-6: Unexpected trailing comma.
(comma-dangle)
[error] 12-19: Replace
⏎··createContext,⏎··useCallback,⏎··useContext,⏎··useEffect,⏎··useRef,⏎··useState,⏎
with·createContext,·useCallback,·useContext,·useEffect,·useRef,·useState·
(prettier/prettier)
[error] 18-18: Unexpected trailing comma.
(comma-dangle)