From 7698fc7821fd8181e3a56cc35643bf79ba40864d Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 23 Oct 2023 09:27:33 -0700 Subject: [PATCH] Fixing a couple of forced reflows in our tutorial components (#9733) * fix forced reflows from tutorial components * fix sidebar height in headless tutorials --- theme/tutorial-sidebar.less | 2 +- .../components/tutorial/TutorialCallout.tsx | 48 ++++++++++--------- .../components/tutorial/TutorialContainer.tsx | 9 ++-- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/theme/tutorial-sidebar.less b/theme/tutorial-sidebar.less index 13dcf043796..f8887585421 100644 --- a/theme/tutorial-sidebar.less +++ b/theme/tutorial-sidebar.less @@ -520,8 +520,8 @@ min-width: inherit; max-width: inherit; width: @simulatorWidth; - height: calc(~'100%' - @mainMenuHeight); top: @mainMenuHeight; + bottom: 0; left: 0; .simPanel { diff --git a/webapp/src/components/tutorial/TutorialCallout.tsx b/webapp/src/components/tutorial/TutorialCallout.tsx index 4587a3c7611..e2a561a1b61 100644 --- a/webapp/src/components/tutorial/TutorialCallout.tsx +++ b/webapp/src/components/tutorial/TutorialCallout.tsx @@ -11,7 +11,7 @@ interface TutorialCalloutProps extends React.PropsWithChildren<{}> { } export function TutorialCallout(props: TutorialCalloutProps) { - const { children, className, buttonIcon, buttonLabel } = props; + const { children, className, buttonIcon, buttonLabel, onClick } = props; const [ visible, setVisible ] = React.useState(false); const [ maxHeight, setMaxHeight ] = React.useState("unset"); const [ top, setTop ] = React.useState("unset"); @@ -20,6 +20,8 @@ export function TutorialCallout(props: TutorialCalloutProps) { const contentRef = React.useRef(null); React.useEffect(() => { + if (!visible) return undefined; + function checkSize() { const lowerBuffer = (document.getElementById("editortools")?.clientHeight ?? 0) + 30; if (contentRef.current?.getBoundingClientRect().bottom >= window.innerHeight - lowerBuffer) { @@ -39,41 +41,41 @@ export function TutorialCallout(props: TutorialCalloutProps) { if (contentRef.current) observer.observe(contentRef.current); checkSize(); - return () => observer.disconnect(); - }); - const captureEvent = (e: any) => { + const closeOnOutsideClick = (e: PointerEvent) => { + if (!popupRef?.current?.contains(e.target as Node)) { + setVisible(false); + } + }; + + document.addEventListener("click", closeOnOutsideClick); + + return () => { + observer.disconnect(); + document.removeEventListener("click", closeOnOutsideClick); + } + }, [visible]); + + + const captureEvent = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); e.nativeEvent?.stopImmediatePropagation(); } - const closeCallout = (e: any) => { - document.removeEventListener("click", closeCalloutIfClickedOutside, true); + const closeCallout = React.useCallback(() => { setVisible(false); - } - - const closeCalloutIfClickedOutside = (e: PointerEvent) => { - if (!popupRef?.current?.contains(e.target as Node)) { - closeCallout(e); - } - } + }, []); - const toggleCallout = (e: any) => { + const toggleCallout = React.useCallback((e: React.MouseEvent) => { captureEvent(e); - if (!visible) { - document.addEventListener("click", closeCalloutIfClickedOutside, true); - } else { - document.removeEventListener("click", closeCalloutIfClickedOutside, true); - } setVisible(!visible); - } + }, [visible]); - const handleButtonClick = (e: any) => { - const { onClick } = props; + const handleButtonClick = React.useCallback((e: React.MouseEvent) => { if (onClick) onClick(visible); toggleCallout(e); - } + }, [onClick, visible]); const buttonTitle = lf("Click to show a hint!"); return
diff --git a/webapp/src/components/tutorial/TutorialContainer.tsx b/webapp/src/components/tutorial/TutorialContainer.tsx index da2ca103e56..97d595b53e7 100644 --- a/webapp/src/components/tutorial/TutorialContainer.tsx +++ b/webapp/src/components/tutorial/TutorialContainer.tsx @@ -51,14 +51,15 @@ export function TutorialContainer(props: TutorialContainerProps) { const showImmersiveReader = pxt.appTarget.appTheme.immersiveReader; const isHorizontal = props.tutorialSimSidebar || pxt.BrowserUtils.isTabletSize(); + const container = React.useRef(); + React.useEffect(() => { const observer = new ResizeObserver(updateScrollGradient); observer.observe(document.body) // We also want to update the scroll gradient if the tutorial wrapper is resized by the user. - const parent = document.querySelector("#tutorialWrapper"); - if (parent) { - observer.observe(parent); + if (container.current) { + observer.observe(container.current); } return () => observer.disconnect(); @@ -248,7 +249,7 @@ export function TutorialContainer(props: TutorialContainerProps) { const hasHint = !!hintMarkdown; - return
+ return
{!isHorizontal && stepCounter}