From 0fb561554d18216d11de8fe691a4c47821ce8578 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 24 Jan 2025 16:05:30 +0100 Subject: [PATCH 1/4] enable scrolling via keyboard buttons --- src/components/platformFilter/style.module.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/platformFilter/style.module.scss b/src/components/platformFilter/style.module.scss index 83e749959699d..b700575f1897a 100644 --- a/src/components/platformFilter/style.module.scss +++ b/src/components/platformFilter/style.module.scss @@ -95,10 +95,6 @@ background: var(--gray-a8); } - &::-webkit-scrollbar-button { - display: none; - } - // Firefox scrollbar scrollbar-width: thin; scrollbar-color: var(--gray-a6) var(--gray-a2); From 59852e549245d54e7feed5e09f62e78f8d8ea008 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 24 Jan 2025 16:41:14 +0100 Subject: [PATCH 2/4] conditionally add event listeners in search --- src/components/search/index.tsx | 5 +++-- ...vigate.tsx => useListKeyboardNavigate.tsx} | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) rename src/hooks/{useKeyboardNavigate.tsx => useListKeyboardNavigate.tsx} (81%) diff --git a/src/components/search/index.tsx b/src/components/search/index.tsx index 5b75b757852e8..d81ce2fd5ff5b 100644 --- a/src/components/search/index.tsx +++ b/src/components/search/index.tsx @@ -15,7 +15,7 @@ import {usePathname, useRouter} from 'next/navigation'; import algoliaInsights from 'search-insights'; import {useOnClickOutside} from 'sentry-docs/clientUtils'; -import {useKeyboardNavigate} from 'sentry-docs/hooks/useKeyboardNavigate'; +import {useListKeyboardNavigate} from 'sentry-docs/hooks/useListKeyboardNavigate'; import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs'; import styles from './search.module.scss'; @@ -181,9 +181,10 @@ export function Search({path, autoFocus, searchPlatforms = [], showChatBot}: Pro [] ); - const {focused} = useKeyboardNavigate({ + const {focused} = useListKeyboardNavigate({ list: flatHits, onSelect: hit => router.push(relativizeUrl(hit.url)), + disableEventListeners: !inputFocus, }); const trackSearchResultClick = useCallback((hit: Hit, position: number): void => { diff --git a/src/hooks/useKeyboardNavigate.tsx b/src/hooks/useListKeyboardNavigate.tsx similarity index 81% rename from src/hooks/useKeyboardNavigate.tsx rename to src/hooks/useListKeyboardNavigate.tsx index ffe6bf0b41bde..0f3339eca2ce7 100644 --- a/src/hooks/useKeyboardNavigate.tsx +++ b/src/hooks/useListKeyboardNavigate.tsx @@ -1,10 +1,15 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; type Props = { + /** + * Whether to disable the keyboard event listeners conditionally + */ + disableEventListeners: boolean; /** * The list of values to navigate through */ list: T[]; + /** * Callback triggered when the item is selected */ @@ -14,7 +19,7 @@ type Props = { /** * Navigate a list of items using the up/down arrow and ^j/^k keys */ -function useKeyboardNavigate({list, onSelect}: Props) { +function useListKeyboardNavigate({list, onSelect, disableEventListeners}: Props) { const [focused, setFocus] = useState(null); const setFocusIndex = useCallback( @@ -85,11 +90,15 @@ function useKeyboardNavigate({list, onSelect}: Props) { ); useEffect(() => { - document.addEventListener('keydown', handleNavigate); - return () => document.removeEventListener('keydown', handleNavigate); - }, [handleNavigate]); + if (!disableEventListeners) { + document.addEventListener('keydown', handleNavigate); + return () => document.removeEventListener('keydown', handleNavigate); + } + + return undefined; + }, [disableEventListeners, handleNavigate]); return {focused, setFocus}; } -export {useKeyboardNavigate}; +export {useListKeyboardNavigate}; From 536fe71c5201e3029fc7e6584b8a609a5aeb1a37 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 24 Jan 2025 16:42:14 +0100 Subject: [PATCH 3/4] undo first try --- src/components/platformFilter/style.module.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/platformFilter/style.module.scss b/src/components/platformFilter/style.module.scss index b700575f1897a..83e749959699d 100644 --- a/src/components/platformFilter/style.module.scss +++ b/src/components/platformFilter/style.module.scss @@ -95,6 +95,10 @@ background: var(--gray-a8); } + &::-webkit-scrollbar-button { + display: none; + } + // Firefox scrollbar scrollbar-width: thin; scrollbar-color: var(--gray-a6) var(--gray-a2); From 78bb6fa2ebb08062c8af4d276000de48a11b692f Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 24 Jan 2025 17:12:28 +0100 Subject: [PATCH 4/4] refactor --- src/components/search/index.tsx | 107 +++---------------- src/components/search/searchResultItems.tsx | 111 ++++++++++++++++++++ src/components/search/util.ts | 7 ++ src/hooks/useListKeyboardNavigate.tsx | 16 +-- 4 files changed, 134 insertions(+), 107 deletions(-) create mode 100644 src/components/search/searchResultItems.tsx create mode 100644 src/components/search/util.ts diff --git a/src/components/search/index.tsx b/src/components/search/index.tsx index d81ce2fd5ff5b..a5fc3c2f0068e 100644 --- a/src/components/search/index.tsx +++ b/src/components/search/index.tsx @@ -9,19 +9,19 @@ import { SentryGlobalSearch, standardSDKSlug, } from '@sentry-internal/global-search'; -import DOMPurify from 'dompurify'; -import Link from 'next/link'; -import {usePathname, useRouter} from 'next/navigation'; +import {usePathname} from 'next/navigation'; import algoliaInsights from 'search-insights'; import {useOnClickOutside} from 'sentry-docs/clientUtils'; -import {useListKeyboardNavigate} from 'sentry-docs/hooks/useListKeyboardNavigate'; import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs'; import styles from './search.module.scss'; import {Logo} from '../logo'; +import {SearchResultItems} from './searchResultItems'; +import {relativizeUrl} from './util'; + // Initialize Algolia Insights algoliaInsights('init', { appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID, @@ -33,8 +33,6 @@ algoliaInsights('init', { // treat it as a random user. const randomUserToken = crypto.randomUUID(); -const MAX_HITS = 10; - // this type is not exported from the global-search package type SentryGlobalSearchConfig = ConstructorParameters[0]; @@ -59,12 +57,6 @@ const userDocsSites: SentryGlobalSearchConfig = [ const config = isDeveloperDocs ? developerDocsSites : userDocsSites; const search = new SentryGlobalSearch(config); -function relativizeUrl(url: string) { - return isDeveloperDocs - ? url - : url.replace(/^(https?:\/\/docs\.sentry\.io)(?=\/|$)/, ''); -} - type Props = { autoFocus?: boolean; path?: string; @@ -79,7 +71,7 @@ export function Search({path, autoFocus, searchPlatforms = [], showChatBot}: Pro const [inputFocus, setInputFocus] = useState(false); const [showOffsiteResults, setShowOffsiteResults] = useState(false); const [loading, setLoading] = useState(true); - const router = useRouter(); + const pathname = usePathname(); const handleClickOutside = useCallback((ev: MouseEvent) => { @@ -176,17 +168,6 @@ export function Search({path, autoFocus, searchPlatforms = [], showChatBot}: Pro const totalHits = results.reduce((a, x) => a + x.hits.length, 0); - const flatHits = results.reduce( - (items, item) => [...items, ...item.hits.slice(0, MAX_HITS)], - [] - ); - - const {focused} = useListKeyboardNavigate({ - list: flatHits, - onSelect: hit => router.push(relativizeUrl(hit.url)), - disableEventListeners: !inputFocus, - }); - const trackSearchResultClick = useCallback((hit: Hit, position: number): void => { try { algoliaInsights('clickedObjectIDsAfterSearch', { @@ -306,77 +287,13 @@ export function Search({path, autoFocus, searchPlatforms = [], showChatBot}: Pro {loading && } {!loading && totalHits > 0 && ( -
- {results - .filter(x => x.hits.length > 0) - .map((result, i) => ( - - {showOffsiteResults && ( -

- From {result.name} -

- )} -
    - {result.hits.slice(0, MAX_HITS).map((hit, index) => ( -
  • el?.scrollIntoView({block: 'nearest'}) - : undefined - } - > - handleSearchResultClick(e, hit, index)} - > - {hit.title && ( -
    - -
    - )} - {hit.text && ( - - )} - {hit.context && ( -
    - {hit.context.context1 && ( -
    - {hit.context.context1} -
    - )} - {hit.context.context2 && ( -
    - {hit.context.context2} -
    - )} -
    - )} - -
  • - ))} -
-
- ))} -
+ + handleSearchResultClick(event, hit, position) + } + showOffsiteResults={showOffsiteResults} + /> )} {!loading && totalHits === 0 && ( diff --git a/src/components/search/searchResultItems.tsx b/src/components/search/searchResultItems.tsx new file mode 100644 index 0000000000000..06585d682751c --- /dev/null +++ b/src/components/search/searchResultItems.tsx @@ -0,0 +1,111 @@ +import {Fragment} from 'react'; +import {Hit, Result} from '@sentry-internal/global-search'; +import DOMPurify from 'dompurify'; +import Link from 'next/link'; +import {useRouter} from 'next/navigation'; + +import {useListKeyboardNavigate} from 'sentry-docs/hooks/useListKeyboardNavigate'; + +import styles from './search.module.scss'; + +import {relativizeUrl} from './util'; + +const MAX_HITS = 10; + +interface SearchResultClickHandler { + event: React.MouseEvent; + hit: Hit; + position: number; +} + +export function SearchResultItems({ + results, + showOffsiteResults, + onSearchResultClick, +}: { + onSearchResultClick: (params: SearchResultClickHandler) => void; + results: Result[]; + showOffsiteResults: boolean; +}) { + const router = useRouter(); + const flatHits = results.reduce( + (items, item) => [...items, ...item.hits.slice(0, MAX_HITS)], + [] + ); + const {focused} = useListKeyboardNavigate({ + list: flatHits, + onSelect: hit => router.push(relativizeUrl(hit.url)), + }); + + return ( +
+ {results + .filter(x => x.hits.length > 0) + .map((result, i) => ( + + {showOffsiteResults && ( +

From {result.name}

+ )} +
    + {result.hits.slice(0, MAX_HITS).map((hit, index) => ( +
  • el?.scrollIntoView({block: 'nearest'}) + : undefined + } + > + onSearchResultClick({event, hit, position: index})} + > + {hit.title && ( +
    + +
    + )} + {hit.text && ( + + )} + {hit.context && ( +
    + {hit.context.context1 && ( +
    + {hit.context.context1} +
    + )} + {hit.context.context2 && ( +
    + {hit.context.context2} +
    + )} +
    + )} + +
  • + ))} +
+
+ ))} +
+ ); +} diff --git a/src/components/search/util.ts b/src/components/search/util.ts new file mode 100644 index 0000000000000..f5191b930da18 --- /dev/null +++ b/src/components/search/util.ts @@ -0,0 +1,7 @@ +import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs'; + +export function relativizeUrl(url: string) { + return isDeveloperDocs + ? url + : url.replace(/^(https?:\/\/docs\.sentry\.io)(?=\/|$)/, ''); +} diff --git a/src/hooks/useListKeyboardNavigate.tsx b/src/hooks/useListKeyboardNavigate.tsx index 0f3339eca2ce7..9a841146c4d11 100644 --- a/src/hooks/useListKeyboardNavigate.tsx +++ b/src/hooks/useListKeyboardNavigate.tsx @@ -1,10 +1,6 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; type Props = { - /** - * Whether to disable the keyboard event listeners conditionally - */ - disableEventListeners: boolean; /** * The list of values to navigate through */ @@ -19,7 +15,7 @@ type Props = { /** * Navigate a list of items using the up/down arrow and ^j/^k keys */ -function useListKeyboardNavigate({list, onSelect, disableEventListeners}: Props) { +function useListKeyboardNavigate({list, onSelect}: Props) { const [focused, setFocus] = useState(null); const setFocusIndex = useCallback( @@ -90,13 +86,9 @@ function useListKeyboardNavigate({list, onSelect, disableEventListeners}: Pro ); useEffect(() => { - if (!disableEventListeners) { - document.addEventListener('keydown', handleNavigate); - return () => document.removeEventListener('keydown', handleNavigate); - } - - return undefined; - }, [disableEventListeners, handleNavigate]); + document.addEventListener('keydown', handleNavigate); + return () => document.removeEventListener('keydown', handleNavigate); + }, [handleNavigate]); return {focused, setFocus}; }