diff --git a/app/components/Sidebar/components/NavLink.tsx b/app/components/Sidebar/components/NavLink.tsx index b318df3b1..90e5cc569 100644 --- a/app/components/Sidebar/components/NavLink.tsx +++ b/app/components/Sidebar/components/NavLink.tsx @@ -11,6 +11,7 @@ import { } from "react-router"; import { Link } from "react-router-dom"; import scrollIntoView from "smooth-scroll-into-view-if-needed"; +import history from "~/utils/history"; const resolveToLocation = ( to: LocationDescriptor | ((location: Location) => LocationDescriptor), @@ -39,6 +40,7 @@ export type Props = React.AnchorHTMLAttributes & { location?: Location; strict?: boolean; to: LocationDescriptor; + onBeforeClick?: () => void; }; /** @@ -55,11 +57,16 @@ const NavLink = ({ strict, style: styleProp, scrollIntoViewIfNeeded, + onClick, + onBeforeClick, to, ...rest }: Props) => { const linkRef = React.useRef(null); const context = React.useContext(RouterContext); + const [preActive, setPreActive] = React.useState( + undefined + ); const currentLocation = locationProp || context.location; const toLocation = normalizeToLocation( resolveToLocation(to, currentLocation), @@ -77,9 +84,9 @@ const NavLink = ({ }) : null; - const isActive = !!(isActiveProp - ? isActiveProp(match, currentLocation) - : match); + const isActive = + preActive ?? + !!(isActiveProp ? isActiveProp(match, currentLocation) : match); const className = isActive ? joinClassnames(classNameProp, activeClassName) : classNameProp; @@ -94,15 +101,62 @@ const NavLink = ({ } }, [linkRef, scrollIntoViewIfNeeded, isActive]); - const props = { - "aria-current": (isActive && ariaCurrent) || undefined, - className, - style, - to: toLocation, - ...rest, + const shouldHandleEvent = ( + event: React.MouseEvent + ): boolean => { + return ( + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore everything but left clicks + !rest.target && // let browser handle "target=_blank" etc. + !event.altKey && + !event.metaKey && + !event.ctrlKey + ); }; - return ; + const handleClick = (event: React.MouseEvent) => { + onClick?.(event); + + if (shouldHandleEvent(event)) { + event.stopPropagation(); + event.preventDefault(); + event.currentTarget.focus(); + + setPreActive(true); + + // Wait one frame until following link + setTimeout(() => { + requestAnimationFrame(executeLink); + event.currentTarget?.blur(); + }, 10); + } + }; + + const executeLink = () => { + history.push(to); + }; + + React.useEffect(() => { + setPreActive(undefined); + }, [currentLocation]); + + return ( + { + if (shouldHandleEvent(event)) { + event.stopPropagation(); + event.preventDefault(); + } + }} + aria-current={(isActive && ariaCurrent) || undefined} + className={className} + style={style} + to={toLocation} + {...rest} + /> + ); }; export default NavLink;