feat: Automatically scroll to active item in sidebar (#1858)

This commit is contained in:
Tom Moor
2021-02-01 19:29:54 -08:00
committed by GitHub
parent 3f267d7745
commit a9d60d288e
6 changed files with 117 additions and 20 deletions

View File

@@ -21,7 +21,6 @@ type Props = {|
canUpdate: boolean,
collection?: Collection,
activeDocument: ?Document,
activeDocumentRef?: (?HTMLElement) => void,
prefetchDocument: (documentId: string) => Promise<void>,
depth: number,
index: number,
@@ -33,7 +32,6 @@ function DocumentLink({
canUpdate,
collection,
activeDocument,
activeDocumentRef,
prefetchDocument,
depth,
index,
@@ -213,7 +211,6 @@ function DocumentLink({
<div ref={dropToReparent}>
<DropToImport documentId={node.id} activeClassName="activeDropZone">
<SidebarLink
innerRef={isActiveDocument ? activeDocumentRef : undefined}
onMouseEnter={handleMouseEnter}
to={{
pathname: node.url,

View File

@@ -0,0 +1,105 @@
// @flow
// ref: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js
// This file is pulled almost 100% from react-router with the addition of one
// thing, automatic scroll to the active link. It's worth the copy paste because
// it avoids recalculating the link match again.
import { createLocation } from "history";
import * as React from "react";
import {
__RouterContext as RouterContext,
matchPath,
type Location,
} from "react-router";
import { Link } from "react-router-dom";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
const resolveToLocation = (to, currentLocation) =>
typeof to === "function" ? to(currentLocation) : to;
const normalizeToLocation = (to, currentLocation) => {
return typeof to === "string"
? createLocation(to, null, null, currentLocation)
: to;
};
const joinClassnames = (...classnames) => {
return classnames.filter((i) => i).join(" ");
};
type Props = {|
activeClassName?: String,
activeStyle?: Object,
className?: string,
exact?: boolean,
isActive?: any,
location?: Location,
strict?: boolean,
style?: Object,
to: string,
|};
/**
* A <Link> wrapper that knows if it's "active" or not.
*/
const NavLink = ({
"aria-current": ariaCurrent = "page",
activeClassName = "active",
activeStyle,
className: classNameProp,
exact,
isActive: isActiveProp,
location: locationProp,
strict,
style: styleProp,
to,
...rest
}: Props) => {
const linkRef = React.useRef();
const context = React.useContext(RouterContext);
const currentLocation = locationProp || context.location;
const toLocation = normalizeToLocation(
resolveToLocation(to, currentLocation),
currentLocation
);
const { pathname: path } = toLocation;
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
const escapedPath = path && path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
const match = escapedPath
? matchPath(currentLocation.pathname, {
path: escapedPath,
exact,
strict,
})
: null;
const isActive = !!(isActiveProp
? isActiveProp(match, currentLocation)
: match);
const className = isActive
? joinClassnames(classNameProp, activeClassName)
: classNameProp;
const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
React.useEffect(() => {
if (isActive && linkRef.current) {
scrollIntoView(linkRef.current, {
scrollMode: "if-needed",
behavior: "instant",
});
}
}, [linkRef, isActive]);
const props = {
"aria-current": (isActive && ariaCurrent) || null,
className,
style,
to: toLocation,
...rest,
};
return <Link ref={linkRef} {...props} />;
};
export default NavLink;

View File

@@ -5,7 +5,7 @@ import Flex from "components/Flex";
const Section = styled(Flex)`
position: relative;
flex-direction: column;
margin: 24px 8px;
margin: 20px 8px;
min-width: ${(props) => props.theme.sidebarMinWidth}px;
flex-shrink: 0;
`;

View File

@@ -1,14 +1,10 @@
// @flow
import * as React from "react";
import {
withRouter,
NavLink,
type RouterHistory,
type Match,
} from "react-router-dom";
import { withRouter, type RouterHistory, type Match } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import EventBoundary from "components/EventBoundary";
import NavLink from "./NavLink";
import { type Theme } from "types";
type Props = {
@@ -47,7 +43,6 @@ function SidebarLink({
theme,
exact,
href,
innerRef,
depth,
history,
match,
@@ -66,14 +61,14 @@ function SidebarLink({
...style,
};
const activeFontWeightOnly = {
const activeDropStyle = {
fontWeight: 600,
};
return (
<StyledNavLink
<Link
$isActiveDrop={isActiveDrop}
activeStyle={isActiveDrop ? activeFontWeightOnly : activeStyle}
activeStyle={isActiveDrop ? activeDropStyle : activeStyle}
style={active ? activeStyle : style}
onClick={onClick}
onMouseEnter={onMouseEnter}
@@ -81,13 +76,12 @@ function SidebarLink({
to={to}
as={to ? undefined : href ? "a" : "div"}
href={href}
ref={innerRef}
className={className}
>
{icon && <IconWrapper>{icon}</IconWrapper>}
<Label>{label}</Label>
{menu && <Actions showActions={showActions}>{menu}</Actions>}
</StyledNavLink>
</Link>
);
}
@@ -121,7 +115,7 @@ const Actions = styled(EventBoundary)`
}
`;
const StyledNavLink = styled(NavLink)`
const Link = styled(NavLink)`
display: flex;
position: relative;
text-overflow: ellipsis;
@@ -138,7 +132,7 @@ const StyledNavLink = styled(NavLink)`
svg {
${(props) => (props.$isActiveDrop ? `fill: ${props.theme.white};` : "")}
transition: fill 50ms
transition: fill 50ms;
}
&:hover {

View File

@@ -162,6 +162,7 @@
"slate": "0.45.0",
"slate-md-serializer": "5.5.4",
"slug": "^1.0.0",
"smooth-scroll-into-view-if-needed": "^1.1.29",
"socket.io": "^2.3.0",
"socket.io-redis": "^5.4.0",
"socketio-auth": "^0.1.1",
@@ -211,4 +212,4 @@
"js-yaml": "^3.13.1"
},
"version": "0.52.0"
}
}

View File

@@ -11028,7 +11028,7 @@ slugify@^1.4.0:
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.6.tgz#ef288d920a47fb01c2be56b3487b6722f5e34ace"
integrity sha512-ZdJIgv9gdrYwhXqxsH9pv7nXxjUEyQ6nqhngRxoAAOlmMGA28FDq5O4/5US4G2/Nod7d1ovNcgURQJ7kHq50KQ==
smooth-scroll-into-view-if-needed@^1.1.27:
smooth-scroll-into-view-if-needed@^1.1.27, smooth-scroll-into-view-if-needed@^1.1.29:
version "1.1.29"
resolved "https://registry.yarnpkg.com/smooth-scroll-into-view-if-needed/-/smooth-scroll-into-view-if-needed-1.1.29.tgz#4f532d9f0353dbca122e546fb062e7b5e0643734"
integrity sha512-UxvIEbmMEqwbw0aZI4SOAtwwkMaLYVION20bDQmazVp3sNb1+WIA5koukqoJizRuAAUANRmcBcrTnodcB7maqw==