Skip to content

Commit

Permalink
Improve snippets scrolling behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
felixhabib committed Sep 5, 2024
1 parent ae7053f commit b79504a
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-carrots-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'playroom': patch
---

Edit later
1 change: 0 additions & 1 deletion src/Playroom/Snippets/Snippets.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export const snippet = style([
backgroundColor: colorPaletteVars.background.selection,
borderRadius: vars.radii.medium,
opacity: 0,
transition: vars.transition.slow,
pointerEvents: 'none',
},
},
Expand Down
57 changes: 4 additions & 53 deletions src/Playroom/Snippets/Snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<string>('');
const [highlightedIndex, setHighlightedIndex] =
Expand All @@ -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]
Expand Down Expand Up @@ -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 (
Expand Down
37 changes: 37 additions & 0 deletions src/Playroom/Snippets/useScrollIntoView.ts
Original file line number Diff line number Diff line change
@@ -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]);
}

0 comments on commit b79504a

Please sign in to comment.