|
1 | 1 | import * as React from "react";
|
2 |
| -import Link from "./Link.js"; |
3 |
| -import useLocation from "./useLocation.js"; |
4 |
| -import type { Location } from "./types"; |
| 2 | +import Link, { type LinkProps } from "./Link.js"; |
| 3 | +import useRouterContext from "./internal/useRouterContext.js"; |
| 4 | +import locationToUrl from "./internal/locationToUrl.js"; |
| 5 | +import type { RouteCondition } from "./types.js"; |
5 | 6 |
|
6 |
| -type LinkProps = React.ComponentProps<typeof Link>; |
7 |
| -type Props = Omit<LinkProps, "className" | "style"> & { |
8 |
| - activeClassName?: string | undefined; |
9 |
| - activeStyle?: React.CSSProperties | undefined; |
10 |
| - exact?: boolean | undefined; |
11 |
| - strict?: boolean | undefined; |
12 |
| - isActive?(match: unknown, location: Location): boolean; |
13 |
| - location?: Location | undefined; |
14 |
| - className?: string | ((isActive: boolean) => string) | undefined; |
| 7 | +export type NavLinkProps = Omit< |
| 8 | + LinkProps, |
| 9 | + "className" | "style" | "children" |
| 10 | +> & { |
| 11 | + children?: React.ReactNode | ((condition: RouteCondition) => React.ReactNode); |
| 12 | + className?: string | ((condition: RouteCondition) => string | undefined); |
15 | 13 | style?:
|
16 | 14 | | React.CSSProperties
|
17 |
| - | ((isActive: boolean) => React.CSSProperties) |
18 |
| - | undefined; |
19 |
| - sensitive?: boolean | undefined; |
| 15 | + | ((condition: RouteCondition) => React.CSSProperties | undefined); |
20 | 16 | };
|
21 |
| - |
22 |
| -const NavLink: React.FC<Props> = ({ |
23 |
| - exact, |
24 |
| - strict, |
25 |
| - isActive, |
26 |
| - location, |
27 |
| - activeStyle, |
28 |
| - activeClassName, |
| 17 | +const NavLink: React.FC<NavLinkProps> = ({ |
29 | 18 | className,
|
30 | 19 | style,
|
31 | 20 | children,
|
32 | 21 | ...rest
|
33 | 22 | }) => {
|
34 |
| - const attrs: LinkProps = { ...rest }; |
35 |
| - if (typeof attrs.to !== "string") { |
36 |
| - throw new Error("NavLink only supports string locations"); |
| 23 | + const context = useRouterContext(); |
| 24 | + const attrs: LinkProps = rest; |
| 25 | + const target = locationToUrl(attrs.to, context.base).toString(); |
| 26 | + const current = locationToUrl(context.location, context.base).toString(); |
| 27 | + const isActive = target === current; |
| 28 | + const condition: RouteCondition = { isActive }; |
| 29 | + if (typeof className === "function") { |
| 30 | + attrs.className = className(condition); |
| 31 | + } else if (isActive) { |
| 32 | + attrs.className = className ? `${className} active` : "active"; |
| 33 | + } else { |
| 34 | + attrs.className = className; |
37 | 35 | }
|
38 | 36 |
|
39 |
| - const { pathname } = useLocation(); |
40 |
| - const active = exact ? pathname === attrs.to : pathname.startsWith(attrs.to); |
| 37 | + attrs.style = typeof style === "function" ? style(condition) : style; |
41 | 38 |
|
42 |
| - if (active) { |
43 |
| - if (activeClassName) { |
44 |
| - attrs.className = attrs.className |
45 |
| - ? `${attrs.className} ${activeClassName}` |
46 |
| - : activeClassName; |
47 |
| - } |
48 |
| - if (activeStyle) { |
49 |
| - attrs.style = attrs.style |
50 |
| - ? { ...attrs.style, ...activeStyle } |
51 |
| - : activeStyle; |
52 |
| - } |
53 |
| - } |
54 |
| - return React.createElement(Link, attrs, children); |
| 39 | + return React.createElement( |
| 40 | + Link, |
| 41 | + attrs, |
| 42 | + typeof children === "function" ? children(condition) : children |
| 43 | + ); |
55 | 44 | };
|
56 | 45 | export default NavLink;
|
0 commit comments