Compare commits

...

6 Commits

Author SHA1 Message Date
Tom Moor 44c5c1c27a fix: Drag-n-drop 2022-10-19 08:19:53 -04:00
Tom Moor 33aa956b20 fix: Flash of starred documents on Home screen 2022-10-18 23:23:52 -04:00
Tom Moor 9f6f806912 Fade in viewer information 2022-10-18 23:08:30 -04:00
Tom Moor 0208e6e42f Handle keyboard navigation 2022-10-18 22:54:22 -04:00
Tom Moor f088375009 Working fast-click 2022-10-18 22:45:30 -04:00
Tom Moor e8db938655 stash 2022-10-18 21:49:19 -04:00
5 changed files with 97 additions and 23 deletions
+6 -2
View File
@@ -9,6 +9,7 @@ import DocumentMeta from "~/components/DocumentMeta";
import DocumentViews from "~/components/DocumentViews";
import Popover from "~/components/Popover";
import useStores from "~/hooks/useStores";
import Fade from "./Fade";
type Props = {
document: Document;
@@ -23,6 +24,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
const documentViews = useObserver(() => views.inDocument(document.id));
const totalViewers = documentViews.length;
const onlyYou = totalViewers === 1 && documentViews[0].user.id;
const viewsLoadedOnMount = React.useRef(totalViewers > 0);
const popover = usePopoverState({
gutter: 8,
@@ -30,12 +32,14 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
modal: true,
});
const Wrapper = viewsLoadedOnMount.current ? React.Fragment : Fade;
return (
<Meta document={document} to={to} replace {...rest}>
{totalViewers && !isDraft ? (
<PopoverDisclosure {...popover}>
{(props) => (
<>
<Wrapper>
&nbsp;&nbsp;
<a {...props}>
{t("Viewed by")}{" "}
@@ -45,7 +49,7 @@ function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
totalViewers === 1 ? t("person") : t("people")
}`}
</a>
</>
</Wrapper>
)}
</PopoverDisclosure>
) : null}
+7 -2
View File
@@ -54,11 +54,14 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
@observable
isFetching = false;
@observable
isFetchingInitial = !this.props.items?.length;
@observable
fetchCounter = 0;
@observable
renderCount: number = DEFAULT_PAGINATION_LIMIT;
renderCount = 15;
@observable
offset = 0;
@@ -85,6 +88,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
this.allowLoadMore = true;
this.renderCount = DEFAULT_PAGINATION_LIMIT;
this.isFetching = false;
this.isFetchingInitial = false;
this.isFetchingMore = false;
};
@@ -112,6 +116,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
}
this.renderCount += limit;
this.isFetchingInitial = false;
} catch (err) {
this.error = err;
} finally {
@@ -159,7 +164,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
const showLoading =
this.isFetching &&
!this.isFetchingMore &&
(!items?.length || this.fetchCounter === 0);
(!items?.length || (this.fetchCounter <= 1 && this.isFetchingInitial));
if (showLoading) {
return (
+83 -17
View File
@@ -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),
@@ -35,14 +36,16 @@ export type Props = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
activeStyle?: React.CSSProperties;
scrollIntoViewIfNeeded?: boolean;
exact?: boolean;
replace?: boolean;
isActive?: (match: match | null, location: Location) => boolean;
location?: Location;
strict?: boolean;
to: LocationDescriptor;
onBeforeClick?: () => void;
};
/**
* A <Link> wrapper that knows if it's "active" or not.
* A <Link> wrapper that clicks extra fast and knows if it's "active" or not.
*/
const NavLink = ({
"aria-current": ariaCurrent = "page",
@@ -53,13 +56,19 @@ const NavLink = ({
isActive: isActiveProp,
location: locationProp,
strict,
replace,
style: styleProp,
scrollIntoViewIfNeeded,
onClick,
onBeforeClick,
to,
...rest
}: Props) => {
const linkRef = React.useRef(null);
const context = React.useContext(RouterContext);
const [preActive, setPreActive] = React.useState<boolean | undefined>(
undefined
);
const currentLocation = locationProp || context.location;
const toLocation = normalizeToLocation(
resolveToLocation(to, currentLocation),
@@ -67,25 +76,24 @@ const NavLink = ({
);
const { pathname: path } = toLocation;
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
const escapedPath = path?.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
const match = escapedPath
const match = path
? matchPath(currentLocation.pathname, {
path: escapedPath,
// Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
path: path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"),
exact,
strict,
})
: null;
const isActive = !!(isActiveProp
? isActiveProp(match, currentLocation)
: match);
const isActive =
preActive ??
!!(isActiveProp ? isActiveProp(match, currentLocation) : match);
const className = isActive
? joinClassnames(classNameProp, activeClassName)
: classNameProp;
const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
React.useEffect(() => {
React.useLayoutEffect(() => {
if (isActive && linkRef.current && scrollIntoViewIfNeeded !== false) {
scrollIntoView(linkRef.current, {
scrollMode: "if-needed",
@@ -94,15 +102,73 @@ const NavLink = ({
}
}, [linkRef, scrollIntoViewIfNeeded, isActive]);
const props = {
"aria-current": (isActive && ariaCurrent) || undefined,
className,
style,
to: toLocation,
...rest,
};
const shouldFastClick = React.useCallback(
(event: React.MouseEvent<HTMLAnchorElement>): boolean => {
return (
event.button === 0 && // Only intercept left clicks
!event.defaultPrevented &&
!rest.target &&
!event.altKey &&
!event.metaKey &&
!event.ctrlKey
);
},
[rest.target]
);
return <Link ref={linkRef} {...props} />;
const navigateTo = React.useCallback(() => {
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}, [to, replace]);
const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLAnchorElement>) => {
onClick?.(event);
if (shouldFastClick(event)) {
event.stopPropagation();
event.preventDefault();
event.currentTarget.focus();
setPreActive(true);
// Wait a frame until following the link
requestAnimationFrame(() => {
requestAnimationFrame(navigateTo);
event.currentTarget?.blur();
});
}
},
[onClick, navigateTo, shouldFastClick]
);
React.useEffect(() => {
setPreActive(undefined);
}, [currentLocation]);
return (
<Link
key={isActive ? "active" : "inactive"}
ref={linkRef}
//onMouseDown={handleClick}
onKeyDown={(event) => {
if (["Enter", " "].includes(event.key)) {
navigateTo();
event.currentTarget?.blur();
}
}}
onClick={handleClick}
aria-current={(isActive && ariaCurrent) || undefined}
className={className}
style={style}
to={toLocation}
replace={replace}
{...rest}
/>
);
};
export default NavLink;
+1
View File
@@ -14,6 +14,7 @@ const TabLink = styled(NavLink)`
align-items: center;
font-weight: 500;
font-size: 14px;
cursor: var(--pointer);
color: ${(props) => props.theme.textTertiary};
margin-right: 24px;
padding: 6px 0;
-2
View File
@@ -23,8 +23,6 @@ function EmptyCollection({ collection }: Props) {
const context = useActionContext();
const collectionName = collection ? collection.name : "";
console.log({ context });
return (
<Centered column>
<Text type="secondary">