Use Next/Link

This commit is contained in:
DrMint 2022-12-05 22:01:46 +01:00
parent 35fdc7af14
commit 6abff354ee
11 changed files with 123 additions and 131 deletions

View File

@ -1,8 +1,7 @@
import React, { MouseEventHandler, useCallback } from "react";
import { MouseEventHandler, useCallback } from "react";
import { Link } from "./Link";
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
@ -45,10 +44,7 @@ export const Button = ({
disabled,
size = "normal",
}: Props): JSX.Element => (
<ConditionalWrapper
isWrapping={isDefinedAndNotEmpty(href) && !disabled}
wrapperProps={{ href: href ?? "", alwaysNewTab }}
wrapper={LinkWrapper}>
<Link href={href} alwaysNewTab={alwaysNewTab} disabled={disabled}>
<div className="relative">
<div
draggable={draggable}
@ -90,7 +86,7 @@ export const Button = ({
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
</div>
</div>
</ConditionalWrapper>
</Link>
);
/*
@ -110,19 +106,3 @@ export const TranslatedButton = ({
return <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />;
};
/*
*
* PRIVATE COMPONENTS
*/
interface LinkWrapperProps {
href: string;
alwaysNewTab: boolean;
}
const LinkWrapper = ({ children, alwaysNewTab, href }: LinkWrapperProps & Wrapper) => (
<Link href={href} alwaysNewTab={alwaysNewTab}>
{children}
</Link>
);

View File

@ -1,74 +1,81 @@
import router from "next/router";
import { MouseEventHandler, useState } from "react";
import { isDefined } from "helpers/others";
import React, { MouseEventHandler } from "react";
import NextLink from "next/link";
import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefinedAndNotEmpty } from "helpers/others";
import { cIf, cJoin } from "helpers/className";
interface Props {
href: string;
href: string | null | undefined;
className?: string;
allowNewTab?: boolean;
alwaysNewTab?: boolean;
children: React.ReactNode;
onClick?: MouseEventHandler<HTMLDivElement>;
onFocusChanged?: (isFocused: boolean) => void;
disabled?: boolean;
linkStyled?: boolean;
}
export const Link = ({
href,
allowNewTab = true,
alwaysNewTab = false,
disabled = false,
children,
className,
onClick,
alwaysNewTab,
disabled,
linkStyled = false,
onFocusChanged,
}: Props): JSX.Element => {
const [isValidClick, setIsValidClick] = useState(false);
return (
<div
className={className}
onMouseLeave={() => {
setIsValidClick(false);
onFocusChanged?.(false);
}: Props): JSX.Element => (
<ConditionalWrapper
isWrapping={isDefinedAndNotEmpty(href) && !disabled}
wrapperProps={{
href: href ?? "",
alwaysNewTab,
onFocusChanged,
className: cJoin(
cIf(
linkStyled,
`underline decoration-dark decoration-dotted underline-offset-2 transition-colors
hover:text-dark`
),
className
),
}}
onContextMenu={(event) => event.preventDefault()}
onMouseDown={(event) => {
if (!disabled) {
event.preventDefault();
onFocusChanged?.(true);
setIsValidClick(true);
}
}}
onMouseUp={(event) => {
onFocusChanged?.(false);
if (!disabled) {
if (isDefined(onClick)) {
onClick(event);
} else if (isValidClick && href) {
if (event.button !== MouseButton.Right) {
if (alwaysNewTab) {
window.open(href, "_blank", "noopener");
} else if (event.button === MouseButton.Left) {
if (href.startsWith("#")) {
router.replace(href);
} else {
router.push(href);
}
} else if (allowNewTab) {
window.open(href, "_blank");
}
}
}
}
}}>
wrapper={LinkWrapper}
wrapperFalse={DisabledWrapper}
wrapperFalseProps={{ className }}>
{children}
</div>
</ConditionalWrapper>
);
};
enum MouseButton {
Left = 0,
Middle = 1,
Right = 2,
interface LinkWrapperProps {
href: string;
className?: string;
alwaysNewTab?: boolean;
onFocusChanged?: (isFocused: boolean) => void;
}
const LinkWrapper = ({
children,
className,
onFocusChanged,
alwaysNewTab = false,
href,
}: LinkWrapperProps & Wrapper) => (
<NextLink
href={href}
className={className}
target={alwaysNewTab ? "_blank" : "_self"}
replace={href.startsWith("#")}
onMouseLeave={() => onFocusChanged?.(false)}
onMouseDown={() => onFocusChanged?.(true)}
onMouseUp={() => onFocusChanged?.(false)}>
{children}
</NextLink>
);
interface DisabledWrapperProps {
className?: string;
}
const DisabledWrapper = ({ children, className }: DisabledWrapperProps & Wrapper) => (
<div className={className}>{children}</div>
);

View File

@ -34,8 +34,8 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
icon={Icon.Favorite}
text={expand ? langui.want_it : undefined}
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
onMouseUp={(event) => event.stopPropagation()}
onClick={() => {
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...current };
newLibraryItemUserStatus[id] =
@ -52,8 +52,8 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
icon={Icon.BackHand}
text={expand ? langui.have_it : undefined}
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
onMouseUp={(event) => event.stopPropagation()}
onClick={() => {
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...current };
newLibraryItemUserStatus[id] =

View File

@ -1,6 +1,5 @@
import Markdown from "markdown-to-jsx";
import { useRouter } from "next/router";
import React, { Fragment } from "react";
import React, { Fragment, useMemo } from "react";
import ReactDOMServer from "react-dom/server";
import { HorizontalLine } from "components/HorizontalLine";
import { Img } from "components/Img";
@ -15,6 +14,7 @@ import { Ico, Icon } from "components/Ico";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { Link } from "components/Inputs/Link";
/*
*
@ -30,7 +30,6 @@ interface MarkdawnProps {
export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => {
const playerName = useAtomGetter(atoms.settings.playerName);
const router = useRouter();
const isContentPanelAtLeastLg = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastLg);
const { showLightBox } = useAtomGetter(atoms.lightBox);
@ -53,13 +52,15 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
component: (compProps: { href: string; children: React.ReactNode }) => {
if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
return (
<a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
<Link href={compProps.href} linkStyled>
{compProps.children}
</Link>
);
}
return (
<a href={compProps.href} target="_blank" rel="noreferrer">
<Link href={compProps.href} alwaysNewTab linkStyled>
{compProps.children}
</a>
</Link>
);
},
},
@ -95,9 +96,9 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
<Link href={`${compProps.page ?? ""}#${slug}`} linkStyled>
{compProps.children}
</a>
</Link>
);
},
},
@ -224,7 +225,6 @@ export const TableOfContents = ({
title,
horizontalLine = false,
}: TableOfContentsProps): JSX.Element => {
const router = useRouter();
const langui = useAtomGetter(atoms.localData.langui);
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
@ -238,9 +238,9 @@ export const TableOfContents = ({
<p
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
text-left">
<a onClick={async () => router.replace(`#${toc.slug}`)}>
<Link href={`#${toc.slug}`} linkStyled>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
</Link>
</p>
<TocLevel tocchildren={toc.children} parentNumbering="" />
</div>
@ -340,8 +340,7 @@ const TocLevel = ({
parentNumbering,
allowIntersection = true,
}: LevelProps): JSX.Element => {
const router = useRouter();
const ids = tocchildren.map((child) => child.slug);
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
const currentIntersection = useIntersectionList(ids);
return (
@ -354,9 +353,9 @@ const TocLevel = ({
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
)}>
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
<a onClick={async () => router.replace(`#${child.slug}`)}>
<Link href={`#${child.slug}`} linkStyled>
{<abbr title={child.title}>{child.title}</abbr>}
</a>
</Link>
</li>
<TocLevel
tocchildren={child.children}

View File

@ -170,11 +170,12 @@ export const MainPanel = (): JSX.Element => {
</p>
)}
<div className="mt-4 mb-8 grid place-content-center">
<a
<Link
onClick={() => sendAnalytics("MainPanel", "Visit license")}
aria-label="Read more about the license we use for this website"
className="group grid grid-flow-col place-content-center gap-1 transition-filter"
href="https://creativecommons.org/licenses/by-sa/4.0/">
href="https://creativecommons.org/licenses/by-sa/4.0/"
alwaysNewTab>
<ColoredSvg
className="h-6 w-6 bg-black group-hover:bg-dark"
src="/icons/creative-commons-brands.svg"
@ -187,7 +188,7 @@ export const MainPanel = (): JSX.Element => {
className="h-6 w-6 bg-black group-hover:bg-dark"
src="/icons/creative-commons-sa-brands.svg"
/>
</a>
</Link>
</div>
{isDefinedAndNotEmpty(langui.copyright_notice) && (
<p>
@ -195,39 +196,36 @@ export const MainPanel = (): JSX.Element => {
</p>
)}
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
<a
<Link
aria-label="Browse our GitHub repository, which include this website source code"
onClick={() => sendAnalytics("MainPanel", "Visit GitHub")}
href="https://github.com/Accords-Library"
target="_blank"
rel="noopener noreferrer">
alwaysNewTab>
<ColoredSvg
className="h-10 w-10 bg-black hover:bg-dark"
src="/icons/github-brands.svg"
/>
</a>
<a
</Link>
<Link
aria-label="Follow us on Twitter"
onClick={() => sendAnalytics("MainPanel", "Visit Twitter")}
href="https://twitter.com/AccordsLibrary"
target="_blank"
rel="noopener noreferrer">
alwaysNewTab>
<ColoredSvg
className="h-10 w-10 bg-black hover:bg-dark"
src="/icons/twitter-brands.svg"
/>
</a>
<a
</Link>
<Link
aria-label="Join our Discord server!"
onClick={() => sendAnalytics("MainPanel", "Visit Discord")}
href="/discord"
target="_blank"
rel="noopener noreferrer">
alwaysNewTab>
<ColoredSvg
className="h-10 w-10 bg-black hover:bg-dark"
src="/icons/discord-brands.svg"
/>
</a>
</Link>
</div>
</div>
</div>

View File

@ -73,7 +73,7 @@ export const SmartList = <T,>({
const [page, setPage] = useState(0);
const langui = useAtomGetter(atoms.localData.langui);
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
useEffect(() => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction, items]);
useEffect(() => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction]);
const searchFilter = useCallback(() => {
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {

View File

@ -1,18 +1,30 @@
import { isDefined } from "./others";
export interface Wrapper {
children: React.ReactNode;
}
interface ConditionalWrapperProps<T> {
interface ConditionalWrapperProps<T, U> {
isWrapping: boolean;
children: React.ReactNode;
wrapper: (wrapperProps: T & Wrapper) => JSX.Element;
wrapperProps: T;
wrapperFalse?: (wrapperProps: U & Wrapper) => JSX.Element;
wrapperFalseProps?: U;
}
export const ConditionalWrapper = <T,>({
export const ConditionalWrapper = <T, U>({
isWrapping,
children,
wrapper: Wrapper,
wrapperFalse: WrapperFalse,
wrapperProps,
}: ConditionalWrapperProps<T>): JSX.Element =>
isWrapping ? <Wrapper {...wrapperProps}>{children}</Wrapper> : <>{children}</>;
wrapperFalseProps,
}: ConditionalWrapperProps<T, U>): JSX.Element =>
isWrapping ? (
<Wrapper {...wrapperProps}>{children}</Wrapper>
) : isDefined(WrapperFalse) && isDefined(wrapperFalseProps) ? (
<WrapperFalse {...wrapperFalseProps}>{children}</WrapperFalse>
) : (
<>{children}</>
);

View File

@ -36,10 +36,9 @@ const AccordsLibraryApp = (props: AppProps): JSX.Element => {
<SettingsPopup />
<LightBoxProvider />
<Script
async
defer
data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID}
src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`}
strategy="lazyOnload"
/>
<props.Component {...props.pageProps} />
</>

View File

@ -18,6 +18,7 @@ import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { Link } from "components/Inputs/Link";
/*
*
@ -95,9 +96,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
: prettyShortenNumber(video.likes)}
</p>
)}
<a href={`https://youtu.be/${video.uid}`} target="_blank" rel="noreferrer">
<Button className="!py-0 !px-3" text={`${langui.view_on} ${video.source}`} />
</a>
<Link href={`https://youtu.be/${video.uid}`} alwaysNewTab>
<Button size="small" text={`${langui.view_on} ${video.source}`} />
</Link>
</div>
</div>
</div>

View File

@ -53,6 +53,7 @@ import { getLangui } from "graphql/fetchLocalData";
import { Ids } from "types/ids";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { Link } from "components/Inputs/Link";
/*
*
@ -713,7 +714,7 @@ const ContentLine = ({
cIf(isOpened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
)}>
<div className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center gap-4">
<a>
<Link href={""} linkStyled>
<h3 className="cursor-pointer" onClick={toggleOpened}>
{selectedTranslation
? prettyInlineTitle(
@ -725,7 +726,7 @@ const ContentLine = ({
? prettySlug(content.slug, parentSlug)
: prettySlug(slug, parentSlug)}
</h3>
</a>
</Link>
<div className="flex flex-row flex-wrap gap-1">
{content?.categories?.map((category, index) => (
<Chip key={index} text={category} />

View File

@ -19,11 +19,6 @@ h6 {
@apply font-headers font-black;
}
a {
@apply cursor-pointer underline decoration-dark decoration-dotted
underline-offset-2 transition-colors hover:text-dark;
}
*::selection {
@apply bg-dark text-light;
}