Formatting menu: Show popup guide faster, fade in/out

This commit is contained in:
Scott Nonnenberg 2023-08-04 09:25:52 -07:00 committed by GitHub
parent 076da53815
commit f597f15faf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 34 deletions

View file

@ -194,6 +194,9 @@
border-radius: 4px; border-radius: 4px;
margin-bottom: 8px; margin-bottom: 8px;
opacity: 0;
transition: opacity 120ms ease-out;
@include light-theme { @include light-theme {
background-color: $color-black; background-color: $color-black;
color: $color-gray-05; color: $color-gray-05;

View file

@ -15,10 +15,10 @@ import * as log from '../../logging/log';
import * as Errors from '../../types/errors'; import * as Errors from '../../types/errors';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { handleOutsideClick } from '../../util/handleOutsideClick'; import { handleOutsideClick } from '../../util/handleOutsideClick';
import { SECOND } from '../../util/durations/constants';
const FADE_OUT_MS = 200; const MENU_FADE_OUT_MS = 200;
const BUTTON_HOVER_TIMEOUT = 2 * SECOND; const POPUP_GUIDE_FADE_MS = 120;
const BUTTON_HOVER_TIMEOUT_MS = 900;
const MENU_TEXT_BUFFER = 12; // pixels const MENU_TEXT_BUFFER = 12; // pixels
// Note: Keyboard shortcuts are defined in the constructor below, and when using // Note: Keyboard shortcuts are defined in the constructor below, and when using
@ -160,7 +160,7 @@ export class FormattingMenu {
this.lastRect = undefined; this.lastRect = undefined;
this.fadingOutTimeout = undefined; this.fadingOutTimeout = undefined;
this.render(); this.render();
}, FADE_OUT_MS); }, MENU_FADE_OUT_MS);
this.render(); this.render();
} }
@ -452,34 +452,47 @@ function FormattingButton({
toggleForStyle: (style: QuillFormattingStyle) => unknown; toggleForStyle: (style: QuillFormattingStyle) => unknown;
}): JSX.Element { }): JSX.Element {
const buttonRef = React.useRef<HTMLButtonElement | null>(null); const buttonRef = React.useRef<HTMLButtonElement | null>(null);
const timerRef = React.useRef<NodeJS.Timeout | undefined>();
const [isHovered, setIsHovered] = React.useState<boolean>(false); const [isHovered, setIsHovered] = React.useState<boolean>(false);
const hoverTimerRef = React.useRef<NodeJS.Timeout | undefined>();
const [isFadingOut, setIsFadingOut] = React.useState<boolean>(false);
const fadeOutTimerRef = React.useRef<NodeJS.Timeout | undefined>();
React.useEffect(() => { React.useEffect(() => {
return () => { return () => {
if (timerRef.current) { if (hoverTimerRef.current) {
clearTimeout(timerRef.current); clearTimeout(hoverTimerRef.current);
timerRef.current = undefined; hoverTimerRef.current = undefined;
}
if (fadeOutTimerRef.current) {
clearTimeout(fadeOutTimerRef.current);
fadeOutTimerRef.current = undefined;
} }
}; };
}, []); }, []);
return ( return (
<> <>
{hasLongHovered && isHovered && buttonRef.current ? ( {(isFadingOut || (hasLongHovered && isHovered)) && buttonRef.current ? (
<Popper placement="top" referenceElement={buttonRef.current}> <Popper placement="top" referenceElement={buttonRef.current}>
{({ ref, style: popperStyles }) => ( {({ ref, style: popperStyles }) => {
const opacity = !popperStyles.transform || isFadingOut ? 0 : 1;
return (
<div <div
className="module-composition-input__format-menu__item__popover" className="module-composition-input__format-menu__item__popover"
ref={ref} ref={ref}
style={popperStyles} style={{ ...popperStyles, opacity }}
> >
{popupGuideText} {popupGuideText}
<div className="module-composition-input__format-menu__item__popover__shortcut"> <div className="module-composition-input__format-menu__item__popover__shortcut">
{popupGuideShortcut} {popupGuideShortcut}
</div> </div>
</div> </div>
)} );
}}
</Popper> </Popper>
) : null} ) : null}
<button <button
@ -499,21 +512,29 @@ function FormattingButton({
toggleForStyle(style); toggleForStyle(style);
}} }}
onMouseEnter={() => { onMouseEnter={() => {
if (timerRef.current) { if (hoverTimerRef.current) {
clearTimeout(timerRef.current); clearTimeout(hoverTimerRef.current);
timerRef.current = undefined; hoverTimerRef.current = undefined;
} }
timerRef.current = setTimeout(() => { hoverTimerRef.current = setTimeout(() => {
onLongHover(true); onLongHover(true);
}, BUTTON_HOVER_TIMEOUT); }, BUTTON_HOVER_TIMEOUT_MS);
setIsHovered(true); setIsHovered(true);
}} }}
onMouseLeave={() => { onMouseLeave={() => {
if (timerRef.current) { if (hoverTimerRef.current) {
clearTimeout(timerRef.current); clearTimeout(hoverTimerRef.current);
timerRef.current = undefined; hoverTimerRef.current = undefined;
}
if (hasLongHovered && isHovered) {
fadeOutTimerRef.current = setTimeout(() => {
setIsFadingOut(false);
fadeOutTimerRef.current = undefined;
}, POPUP_GUIDE_FADE_MS);
setIsFadingOut(true);
} }
setIsHovered(false); setIsHovered(false);

View file

@ -2861,18 +2861,26 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx", "path": "ts/quill/formatting/menu.tsx",
"line": " const buttonRef = React.useRef<HTMLButtonElement | null>(null);", "line": " const fadeOutTimerRef = React.useRef<NodeJS.Timeout | undefined>();",
"reasonCategory": "usageTrusted", "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
"updated": "2023-04-22T00:07:56.294Z", "updated": "2023-08-02T19:01:24.771Z",
"reasonDetail": "Popper needs to reference the button" "reasonDetail": "We need a persistent timer to know when to remove after fade-out"
}, },
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx", "path": "ts/quill/formatting/menu.tsx",
"line": " const timerRef = React.useRef<NodeJS.Timeout | undefined>();", "line": " const hoverTimerRef = React.useRef<NodeJS.Timeout | undefined>();",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
"updated": "2023-08-02T19:01:24.771Z",
"reasonDetail": "We need a persistent timer to track long-hovers"
},
{
"rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx",
"line": " const buttonRef = React.useRef<HTMLButtonElement | null>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-04-22T00:07:56.294Z", "updated": "2023-04-22T00:07:56.294Z",
"reasonDetail": "We need a persistent timer to track long-hovers" "reasonDetail": "Popper needs to reference the button"
}, },
{ {
"rule": "DOM-innerHTML", "rule": "DOM-innerHTML",