NavLink.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import React from "react";
  2. import { __RouterContext as RouterContext, matchPath } from "react-router";
  3. import PropTypes from "prop-types";
  4. import invariant from "tiny-invariant";
  5. import Link from "./Link.js";
  6. import {
  7. resolveToLocation,
  8. normalizeToLocation
  9. } from "./utils/locationUtils.js";
  10. // React 15 compat
  11. const forwardRefShim = C => C;
  12. let { forwardRef } = React;
  13. if (typeof forwardRef === "undefined") {
  14. forwardRef = forwardRefShim;
  15. }
  16. function joinClassnames(...classnames) {
  17. return classnames.filter(i => i).join(" ");
  18. }
  19. /**
  20. * A <Link> wrapper that knows if it's "active" or not.
  21. */
  22. const NavLink = forwardRef(
  23. (
  24. {
  25. "aria-current": ariaCurrent = "page",
  26. activeClassName = "active",
  27. activeStyle,
  28. className: classNameProp,
  29. exact,
  30. isActive: isActiveProp,
  31. location: locationProp,
  32. sensitive,
  33. strict,
  34. style: styleProp,
  35. to,
  36. innerRef, // TODO: deprecate
  37. ...rest
  38. },
  39. forwardedRef
  40. ) => {
  41. return (
  42. <RouterContext.Consumer>
  43. {context => {
  44. invariant(context, "You should not use <NavLink> outside a <Router>");
  45. const currentLocation = locationProp || context.location;
  46. const toLocation = normalizeToLocation(
  47. resolveToLocation(to, currentLocation),
  48. currentLocation
  49. );
  50. const { pathname: path } = toLocation;
  51. // Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
  52. const escapedPath =
  53. path && path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
  54. const match = escapedPath
  55. ? matchPath(currentLocation.pathname, {
  56. path: escapedPath,
  57. exact,
  58. sensitive,
  59. strict
  60. })
  61. : null;
  62. const isActive = !!(isActiveProp
  63. ? isActiveProp(match, currentLocation)
  64. : match);
  65. const className = isActive
  66. ? joinClassnames(classNameProp, activeClassName)
  67. : classNameProp;
  68. const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
  69. const props = {
  70. "aria-current": (isActive && ariaCurrent) || null,
  71. className,
  72. style,
  73. to: toLocation,
  74. ...rest
  75. };
  76. // React 15 compat
  77. if (forwardRefShim !== forwardRef) {
  78. props.ref = forwardedRef || innerRef;
  79. } else {
  80. props.innerRef = innerRef;
  81. }
  82. return <Link {...props} />;
  83. }}
  84. </RouterContext.Consumer>
  85. );
  86. }
  87. );
  88. if (__DEV__) {
  89. NavLink.displayName = "NavLink";
  90. const ariaCurrentType = PropTypes.oneOf([
  91. "page",
  92. "step",
  93. "location",
  94. "date",
  95. "time",
  96. "true"
  97. ]);
  98. NavLink.propTypes = {
  99. ...Link.propTypes,
  100. "aria-current": ariaCurrentType,
  101. activeClassName: PropTypes.string,
  102. activeStyle: PropTypes.object,
  103. className: PropTypes.string,
  104. exact: PropTypes.bool,
  105. isActive: PropTypes.func,
  106. location: PropTypes.object,
  107. sensitive: PropTypes.bool,
  108. strict: PropTypes.bool,
  109. style: PropTypes.object
  110. };
  111. }
  112. export default NavLink;