diff --git a/src/Playroom/Snippets/Snippets.css.ts b/src/Playroom/Snippets/Snippets.css.ts index 51b49886..49e46470 100644 --- a/src/Playroom/Snippets/Snippets.css.ts +++ b/src/Playroom/Snippets/Snippets.css.ts @@ -60,7 +60,6 @@ export const snippet = style([ backgroundColor: colorPaletteVars.background.selection, borderRadius: vars.radii.medium, opacity: 0, - transition: vars.transition.slow, pointerEvents: 'none', }, }, diff --git a/src/Playroom/Snippets/Snippets.tsx b/src/Playroom/Snippets/Snippets.tsx index 348ed793..c60fc4cf 100644 --- a/src/Playroom/Snippets/Snippets.tsx +++ b/src/Playroom/Snippets/Snippets.tsx @@ -9,6 +9,7 @@ import { Strong } from '../Strong/Strong'; import { Text } from '../Text/Text'; import * as styles from './Snippets.css'; +import { useScrollIntoView } from './useScrollIntoView'; type HighlightIndex = number | null; type ReturnedSnippet = Snippet | null; @@ -29,52 +30,6 @@ const filterSnippetsForTerm = (snippets: Props['snippets'], term: string) => .map(({ original, score }) => ({ ...original, score })) : snippets; -const scrollToHighlightedSnippet = ( - listEl: HTMLUListElement | null, - highlightedEl: HTMLLIElement | null -) => { - if (highlightedEl && listEl) { - const scrollStep = Math.max( - Math.ceil(listEl.offsetHeight * 0.25), - highlightedEl.offsetHeight * 2 - ); - const currentListTop = listEl.scrollTop + scrollStep; - const currentListBottom = - listEl.offsetHeight + listEl.scrollTop - scrollStep; - let top = 0; - - if ( - highlightedEl === listEl.firstChild || - highlightedEl === listEl.lastChild - ) { - highlightedEl.scrollIntoView(false); - return; - } - - if (highlightedEl.offsetTop >= currentListBottom) { - top = - highlightedEl.offsetTop - - listEl.offsetHeight + - highlightedEl.offsetHeight + - scrollStep; - } else if (highlightedEl.offsetTop <= currentListTop) { - top = highlightedEl.offsetTop - scrollStep; - } else { - return; - } - - if ('scrollBehavior' in window.document.documentElement.style) { - listEl.scrollTo({ - left: 0, - top, - behavior: 'smooth', - }); - } else { - listEl.scrollTo(0, top); - } - } -}; - export default ({ snippets, onHighlight, onClose }: Props) => { const [searchTerm, setSearchTerm] = useState(''); const [highlightedIndex, setHighlightedIndex] = @@ -94,10 +49,9 @@ export default ({ snippets, onHighlight, onClose }: Props) => { }, 50 ); - const debounceScrollToHighlighted = useDebouncedCallback( - scrollToHighlightedSnippet, - 50 - ); + + useScrollIntoView(highlightedEl.current, listEl.current); + const filteredSnippets = useMemo( () => filterSnippetsForTerm(snippets, searchTerm), [searchTerm, snippets] @@ -125,9 +79,6 @@ export default ({ snippets, onHighlight, onClose }: Props) => { onBlur={() => { setHighlightedIndex(null); }} - onKeyUp={() => { - debounceScrollToHighlighted(listEl.current, highlightedEl.current); - }} onKeyDown={(event) => { if (/^(?:Arrow)?Down$/.test(event.key)) { if ( diff --git a/src/Playroom/Snippets/useScrollIntoView.ts b/src/Playroom/Snippets/useScrollIntoView.ts new file mode 100644 index 00000000..40e5d03e --- /dev/null +++ b/src/Playroom/Snippets/useScrollIntoView.ts @@ -0,0 +1,37 @@ +import { useEffect } from 'react'; + +export function useScrollIntoView( + element: HTMLElement | null, + scrollContainer: HTMLElement | null +) { + useEffect(() => { + if (!scrollContainer || !element) { + return; + } + + const itemOffsetRelativeToContainer = + element.offsetParent === scrollContainer + ? element.offsetTop + : element.offsetTop - scrollContainer.offsetTop; + + let { scrollTop } = scrollContainer; // Top of the visible area + + if (itemOffsetRelativeToContainer < scrollTop) { + // Item is off the top of the visible area + scrollTop = itemOffsetRelativeToContainer; + } else if ( + itemOffsetRelativeToContainer + element.offsetHeight > + scrollTop + scrollContainer.offsetHeight + ) { + // Item is off the bottom of the visible area + scrollTop = + itemOffsetRelativeToContainer + + element.offsetHeight - + scrollContainer.offsetHeight; + } + + if (scrollTop !== scrollContainer.scrollTop) { + scrollContainer.scrollTop = scrollTop; + } + }, [scrollContainer, element]); +}