diff --git a/www/api-reference/components/Search/Hits/index.tsx b/www/api-reference/components/Search/Hits/index.tsx index 4d4acd4d6d..677faa889e 100644 --- a/www/api-reference/components/Search/Hits/index.tsx +++ b/www/api-reference/components/Search/Hits/index.tsx @@ -141,7 +141,7 @@ const SearchHits = ({ indexName, setNoResults }: SearchHitsProps) => { "overflow-x-hidden text-ellipsis whitespace-nowrap break-words", "hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark", "focus:bg-medusa-bg-base-hover dark:focus:bg-medusa-bg-base-hover-dark", - "focus:outline-none" + "last:mb-1 focus:outline-none" )} key={index} tabIndex={index} diff --git a/www/api-reference/components/Search/Modal/index.tsx b/www/api-reference/components/Search/Modal/index.tsx index 1b59140237..e9a4dc6d26 100644 --- a/www/api-reference/components/Search/Modal/index.tsx +++ b/www/api-reference/components/Search/Modal/index.tsx @@ -1,6 +1,6 @@ "use client" -import React, { useEffect, useMemo, useRef, useState } from "react" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import algoliasearch, { SearchClient } from "algoliasearch/lite" import { InstantSearch, SearchBox } from "react-instantsearch" import Modal from "../../Modal" @@ -110,6 +110,17 @@ const SearchModal = () => { useEffect(() => { if (isOpen && searchBoxRef.current) { focusSearchInput() + } else if (!isOpen) { + const focusedItem = modalRef.current?.querySelector( + ":focus" + ) as HTMLElement + if ( + focusedItem && + focusedItem === searchBoxRef.current?.querySelector("input") + ) { + // remove focus + focusedItem.blur() + } } }, [isOpen]) @@ -179,14 +190,51 @@ const SearchModal = () => { } } + const shortcutKeys = useMemo(() => ["ArrowUp", "ArrowDown", "Enter"], []) + useKeyboardShortcut({ metakey: false, - shortcutKeys: ["ArrowUp", "ArrowDown", "Enter"], + shortcutKeys, action: handleKeyAction, checkEditing: false, preventDefault: false, }) + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!isOpen) { + return + } + // check if shortcut keys were pressed + const lowerPressedKey = e.key.toLowerCase() + const pressedShortcut = [...shortcutKeys, "Escape"].some( + (s) => s.toLowerCase() === lowerPressedKey + ) + if (pressedShortcut) { + return + } + + const focusedItem = modalRef.current?.querySelector( + ":focus" + ) as HTMLElement + const searchInput = searchBoxRef.current?.querySelector( + "input" + ) as HTMLInputElement + if (searchInput && focusedItem !== searchInput) { + searchInput.focus() + } + }, + [shortcutKeys, isOpen] + ) + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown) + + return () => { + window.removeEventListener("keydown", handleKeyDown) + } + }, [handleKeyDown]) + return ( { "cursor-pointer rounded-sm p-0.5", "hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark", "focus:bg-medusa-bg-base-hover dark:focus:bg-medusa-bg-base-hover-dark", - "focus:outline-none" + "last:mb-1 focus:outline-none" )} onClick={() => setIndexUiState({ diff --git a/www/docs/src/components/Search/Hits/index.tsx b/www/docs/src/components/Search/Hits/index.tsx index f1869b7b3e..90ba582ba8 100644 --- a/www/docs/src/components/Search/Hits/index.tsx +++ b/www/docs/src/components/Search/Hits/index.tsx @@ -129,7 +129,7 @@ const SearchHits = ({ indexName, setNoResults }: SearchHitsProps) => { "overflow-x-hidden text-ellipsis whitespace-nowrap break-words", "hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark", "focus:bg-medusa-bg-base-hover dark:focus:bg-medusa-bg-base-hover-dark", - "focus:outline-none" + "focus:outline-none last:mb-1" )} key={index} tabIndex={index} diff --git a/www/docs/src/components/Search/Modal/index.tsx b/www/docs/src/components/Search/Modal/index.tsx index 1be624d44a..bc75d05e3a 100644 --- a/www/docs/src/components/Search/Modal/index.tsx +++ b/www/docs/src/components/Search/Modal/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from "react" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import algoliasearch, { SearchClient } from "algoliasearch/lite" import { InstantSearch, SearchBox } from "react-instantsearch" import Modal from "../../Modal" @@ -86,6 +86,17 @@ const SearchModal = () => { useEffect(() => { if (isOpen && searchBoxRef.current) { focusSearchInput() + } else if (!isOpen) { + const focusedItem = modalRef.current?.querySelector( + ":focus" + ) as HTMLElement + if ( + focusedItem && + focusedItem === searchBoxRef.current?.querySelector("input") + ) { + // remove focus + focusedItem.blur() + } } }, [isOpen]) @@ -155,14 +166,51 @@ const SearchModal = () => { } } + const shortcutKeys = useMemo(() => ["ArrowUp", "ArrowDown", "Enter"], []) + useKeyboardShortcut({ metakey: false, - shortcutKeys: ["ArrowUp", "ArrowDown", "Enter"], + shortcutKeys, action: handleKeyAction, checkEditing: false, preventDefault: false, }) + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!isOpen) { + return + } + // check if shortcut keys were pressed + const lowerPressedKey = e.key.toLowerCase() + const pressedShortcut = [...shortcutKeys, "Escape"].some( + (s) => s.toLowerCase() === lowerPressedKey + ) + if (pressedShortcut) { + return + } + + const focusedItem = modalRef.current?.querySelector( + ":focus" + ) as HTMLElement + const searchInput = searchBoxRef.current?.querySelector( + "input" + ) as HTMLInputElement + if (searchInput && focusedItem !== searchInput) { + searchInput.focus() + } + }, + [shortcutKeys, isOpen] + ) + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown) + + return () => { + window.removeEventListener("keydown", handleKeyDown) + } + }, [handleKeyDown]) + return ( { "cursor-pointer rounded-sm p-0.5", "hover:bg-medusa-bg-base-hover dark:hover:bg-medusa-bg-base-hover-dark", "focus:bg-medusa-bg-base-hover dark:focus:bg-medusa-bg-base-hover-dark", - "focus:outline-none" + "focus:outline-none last:mb-1" )} onClick={() => setIndexUiState({