From 4361a643beaada97d4fc2ca838abbbb302c0c7bf Mon Sep 17 00:00:00 2001 From: Noel Chou Date: Fri, 6 Dec 2024 13:54:38 -0500 Subject: [PATCH] scroll to initial selection; scroll to top on search change --- .../src/LanguageChooser.tsx | 161 +++++++++++------- 1 file changed, 104 insertions(+), 57 deletions(-) diff --git a/components/language-chooser/react/language-chooser-react-mui/src/LanguageChooser.tsx b/components/language-chooser/react/language-chooser-react-mui/src/LanguageChooser.tsx index 4e90fa0..b4906f1 100644 --- a/components/language-chooser/react/language-chooser-react-mui/src/LanguageChooser.tsx +++ b/components/language-chooser/react/language-chooser-react-mui/src/LanguageChooser.tsx @@ -35,7 +35,7 @@ import { import { debounce } from "lodash"; import "./styles.css"; import { CustomizeLanguageButton } from "./CustomizeLanguageButton"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { CustomizeLanguageDialog } from "./CustomizeLanguageDialog"; import LazyLoad from "react-lazyload"; import { FuseResult } from "fuse.js"; @@ -112,6 +112,36 @@ export const LanguageChooser: React.FunctionComponent = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // We only want this to run once + // on first load, if there is an initialSelectionLanguageTag, we want to scroll to the selected language card + const [initialScrollingNeeded, setInitialScrollingNeeded] = useState( + !!props.initialSelectionLanguageTag + ); + const selectedLanguageCardRef = useRef(null); + useEffect( + () => { + console.log("initialScrollingNeeded", initialScrollingNeeded); + if ( + initialScrollingNeeded && + props.initialSelectionLanguageTag && + selectedLanguageCardRef.current + ) { + selectedLanguageCardRef.current?.scrollIntoView({ + block: "center", + }); + setInitialScrollingNeeded(false); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + // The ref is not ready yet when this first runs, so we rely on the selectedLanguageCardRef.current dependency to trigger it. + // Even though "Mutable values aren't valid dependencies because mutating them doesn't re-render the component", it still triggers + // the effect which does the scrolling, and then the effect calls setInitialScrollingNeeded which triggers a re-render and so syncs the state back up. + selectedLanguageCardRef.current, + initialScrollingNeeded, + props.initialSelectionLanguageTag, + ] + ); + const [previousStateWasValidSelection, setPreviousStateWasValidSelection] = useState(false); @@ -135,6 +165,12 @@ export const LanguageChooser: React.FunctionComponent = ( } }, [lp.selectedLanguage, lp.selectedScript, lp.customizableLanguageDetails]); + // Scroll to top whenever the language list changes + const languageCardListRef = useRef(null); + useEffect(() => { + languageCardListRef.current?.scrollTo(0, 0); + }, [lp.languageData]); + const [customizeLanguageDialogOpen, setCustomizeLanguageDialogOpen] = useState(false); @@ -237,71 +273,82 @@ export const LanguageChooser: React.FunctionComponent = ( flex-basis: 0; flex-grow: 1; `} + ref={languageCardListRef} > {lp.languageData.map((language, index) => { + const isSelectedLanguageCard = codeMatches( + language.iso639_3_code, + lp.selectedLanguage?.iso639_3_code + ); return ( - - + lp.toggleSelectLanguage(language)} + > + {codeMatches( language.iso639_3_code, lp.selectedLanguage?.iso639_3_code - )} - onClick={() => lp.toggleSelectLanguage(language)} - > - {codeMatches( - language.iso639_3_code, - lp.selectedLanguage?.iso639_3_code - ) && - language.scripts.length > 1 && ( - - {language.scripts.map((script: IScript) => { - return ( - - 1 && ( + + {language.scripts.map((script: IScript) => { + return ( + lp.toggleSelectScript(script)} - /> - - ); - })} - - )} - + > + lp.toggleSelectScript(script)} + /> + + ); + })} + + )} + + ); })}