diff --git a/frontend/src/routing/Link.tsx b/frontend/src/routing/Link.tsx index bcfff2fe7..206cc60f5 100644 --- a/frontend/src/routing/Link.tsx +++ b/frontend/src/routing/Link.tsx @@ -12,18 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { useAtomValue, useSetAtom } from "jotai"; -import { useTransition } from "react"; - import styles from "./Link.module.css"; -import { appConfigAtom, routeAtom } from "./atoms"; -import { Route, routeToPath } from "./routes"; - -// Filter out clicks with modifiers or that have been prevented -const shouldHandleClick = (e: React.MouseEvent): boolean => - !e.defaultPrevented && - e.button === 0 && - !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); +import { Route } from "./routes"; +import { useNavigationLink } from "./useNavigationLink"; const Link: React.FC< { @@ -32,13 +23,7 @@ const Link: React.FC< kind?: "button"; } & React.HTMLProps > = ({ route, children, kind, className, ...props }) => { - const config = useAtomValue(appConfigAtom); - const path = routeToPath(route); - const fullUrl = config.root + path; - const setRoute = useSetAtom(routeAtom); - - // TODO: we should probably have more user control over this - const [isPending, startTransition] = useTransition(); + const { onClick, href, pending } = useNavigationLink(route); const classNames = [ kind === "button" ? styles.linkButton : "", @@ -46,23 +31,8 @@ const Link: React.FC< ].join(""); return ( - { - // Only handle left clicks without modifiers - if (!shouldHandleClick(e)) { - return; - } - - e.preventDefault(); - startTransition(() => { - setRoute(route); - }); - }} - className={classNames} - {...props} - > - {isPending ? "Loading..." : children} + + {pending ? "Loading..." : children} ); }; diff --git a/frontend/src/routing/index.ts b/frontend/src/routing/index.ts index 36432ae60..1e942fc56 100644 --- a/frontend/src/routing/index.ts +++ b/frontend/src/routing/index.ts @@ -18,3 +18,4 @@ export type { Route, Location } from "./routes"; export { pathToRoute, routeToPath } from "./routes"; export { getRouteActionRedirection } from "./actions"; export { routeAtom, locationAtom, appConfigAtom } from "./atoms"; +export { useNavigationLink } from "./useNavigationLink"; diff --git a/frontend/src/routing/useNavigationLink.ts b/frontend/src/routing/useNavigationLink.ts new file mode 100644 index 000000000..a3274c819 --- /dev/null +++ b/frontend/src/routing/useNavigationLink.ts @@ -0,0 +1,56 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { useAtomValue, useSetAtom } from "jotai"; +import { useTransition } from "react"; + +import { appConfigAtom, routeAtom } from "./atoms"; +import { Route, routeToPath } from "./routes"; + +// Filter out clicks with modifiers or that have been prevented +const shouldHandleClick = (e: React.MouseEvent): boolean => + !e.defaultPrevented && + e.button === 0 && + !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); + +/** + * A hook which controls a navigation link to a given route + */ +export const useNavigationLink = ( + route: Route, +): { + onClick: (event: React.MouseEvent) => void; + href: string; + pending: boolean; +} => { + const config = useAtomValue(appConfigAtom); + const path = routeToPath(route); + const href = config.root + path; + const setRoute = useSetAtom(routeAtom); + const [pending, startTransition] = useTransition(); + + const onClick = (e: React.MouseEvent): void => { + // Only handle left clicks without modifiers + if (!shouldHandleClick(e)) { + return; + } + + e.preventDefault(); + startTransition(() => { + setRoute(route); + }); + }; + + return { onClick, href, pending }; +};