"use client" import NextLink from "next/link" import { usePathname, useSearchParams } from "next/navigation" import { Children, type ReactNode, useCallback, useMemo } from "react" import { useCheckIfExternalLink } from "@/hooks/useCheckIfExternalLink" import { trackClick } from "@/utils/tracking" import { linkVariants } from "./variants" import type { LinkProps } from "./link" // We wrap all text nodes to avoid having consumers manually wrap text nodes in spans. // This is so that we can better support underline on links as Material Symbols // are implemented as a font and therefore gets underline. Icons inside links // should not get an underline. function wrapTextNodes(children: ReactNode): ReactNode { return Children.map(children, (child) => { if (typeof child === "string") { return {child} } return child }) } export default function Link({ children, active, className, color, href, partialMatch = false, textDecoration, size, scroll = true, prefetch, variant, weight, trackingId, trackingParams, onClick, /** * Decides if the link should include the current search params in the URL. * If the given href also contains search params, they take precedence and * override any current search params. If you need to merge them, handle that * in your component that passes the href here. */ keepSearchParams, appendToCurrentPath, ...props }: LinkProps) { const currentPageSlug = usePathname() const searchParams = useSearchParams() let isActive = active || currentPageSlug === href if (partialMatch && !isActive) { isActive = currentPageSlug === href } const classNames = linkVariants({ active: isActive, className, textDecoration, color, size, weight, variant, }) const fullUrl = useMemo(() => { let newPath = href if (appendToCurrentPath) { newPath = `${currentPageSlug}${newPath}` } if (keepSearchParams && searchParams.size) { if (newPath.includes("?")) { const newPathParts = newPath.split("?") const newSearchParams = new URLSearchParams(newPathParts[1]) searchParams.forEach((v, k) => { if (!newSearchParams.has(k)) { newSearchParams.set(k, v) } }) return `${newPathParts[0]}?${newSearchParams}` } return `${newPath}?${searchParams}` } return newPath }, [ href, searchParams, keepSearchParams, appendToCurrentPath, currentPageSlug, ]) // TODO: Remove this check (and hook) and only return when current web is deleted const isExternal = useCheckIfExternalLink(href) const trackClickById = useCallback(() => { if (trackingId) { trackClick(trackingId, trackingParams) } }, [trackingId, trackingParams]) const linkProps = { href: fullUrl, className: classNames, } const wrappedChildren = typeof children === "function" ? children : wrapTextNodes(children) return isExternal ? ( { if (onClick) { onClick(e) } }} > {wrappedChildren} ) : ( { if (onClick) { onClick(e) } if (trackingId) { trackClickById() } }} id={trackingId} {...props} {...linkProps} > {wrappedChildren} ) }