feat(search): keyboard-accessible search button (#1331)

* Use a `<button>` for search

* Fix search button styles to match preexisting styling

* Remove additional native button properties.

* Invoke search button on click or keyboard.

* Reorganize search button DOM hierarchy

* Restore focus to the search button when exiting the search overlay

* Run prettier on Search.tsx
This commit is contained in:
Andrew 2024-08-09 21:46:50 -04:00 committed by GitHub
parent 195fc5134c
commit 3b5ed813f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 18 additions and 20 deletions

View file

@ -19,24 +19,16 @@ export default ((userOpts?: Partial<SearchOptions>) => {
const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder
return ( return (
<div class={classNames(displayClass, "search")}> <div class={classNames(displayClass, "search")}>
<div id="search-icon"> <button class="search-button" id="search-button">
<p>{i18n(cfg.locale).components.search.title}</p> <p>{i18n(cfg.locale).components.search.title}</p>
<div></div> <svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7">
<svg <title>Search</title>
tabIndex={0}
aria-labelledby="title desc"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 19.9 19.7"
>
<title id="title">Search</title>
<desc id="desc">Search</desc>
<g class="search-path" fill="none"> <g class="search-path" fill="none">
<path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" /> <path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
<circle cx="8" cy="8" r="7" /> <circle cx="8" cy="8" r="7" />
</g> </g>
</svg> </svg>
</div> </button>
<div id="search-container"> <div id="search-container">
<div id="search-space"> <div id="search-space">
<input <input

View file

@ -148,7 +148,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const data = await fetchData const data = await fetchData
const container = document.getElementById("search-container") const container = document.getElementById("search-container")
const sidebar = container?.closest(".sidebar") as HTMLElement const sidebar = container?.closest(".sidebar") as HTMLElement
const searchIcon = document.getElementById("search-icon") const searchButton = document.getElementById("search-button")
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
const searchLayout = document.getElementById("search-layout") const searchLayout = document.getElementById("search-layout")
const idDataMap = Object.keys(data) as FullSlug[] const idDataMap = Object.keys(data) as FullSlug[]
@ -191,6 +191,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
} }
searchType = "basic" // reset search type after closing searchType = "basic" // reset search type after closing
searchButton?.focus()
} }
function showSearch(searchTypeNew: SearchType) { function showSearch(searchTypeNew: SearchType) {
@ -458,8 +460,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
document.addEventListener("keydown", shortcutHandler) document.addEventListener("keydown", shortcutHandler)
window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler))
searchIcon?.addEventListener("click", () => showSearch("basic")) searchButton?.addEventListener("click", () => showSearch("basic"))
window.addCleanup(() => searchIcon?.removeEventListener("click", () => showSearch("basic"))) window.addCleanup(() => searchButton?.removeEventListener("click", () => showSearch("basic")))
searchBar?.addEventListener("input", onType) searchBar?.addEventListener("input", onType)
window.addCleanup(() => searchBar?.removeEventListener("input", onType)) window.addCleanup(() => searchBar?.removeEventListener("input", onType))

View file

@ -3,6 +3,7 @@ export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb:
function click(this: HTMLElement, e: HTMLElementEventMap["click"]) { function click(this: HTMLElement, e: HTMLElementEventMap["click"]) {
if (e.target !== this) return if (e.target !== this) return
e.preventDefault() e.preventDefault()
e.stopPropagation()
cb() cb()
} }

View file

@ -5,18 +5,21 @@
max-width: 14rem; max-width: 14rem;
flex-grow: 0.3; flex-grow: 0.3;
& > #search-icon { & > .search-button {
background-color: var(--lightgray); background-color: var(--lightgray);
border: none;
border-radius: 4px; border-radius: 4px;
font-family: inherit;
font-size: inherit;
height: 2rem; height: 2rem;
padding: 0;
display: flex; display: flex;
align-items: center; align-items: center;
text-align: inherit;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
width: 100%;
& > div { justify-content: space-between;
flex-grow: 1;
}
& > p { & > p {
display: inline; display: inline;