listLink.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import * as React from 'react';
  2. import {Link, withRouter, WithRouterProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import classNames from 'classnames';
  5. import {LocationDescriptor} from 'history';
  6. import omit from 'lodash/omit';
  7. import * as qs from 'query-string';
  8. type DefaultProps = {
  9. index: boolean;
  10. activeClassName: string;
  11. disabled: boolean;
  12. };
  13. type LinkProps = Omit<React.ComponentProps<typeof Link>, 'to'>;
  14. type Props = WithRouterProps &
  15. Partial<DefaultProps> &
  16. LinkProps & {
  17. /**
  18. * Link target. We don't want to expose the ToLocationFunction on this component.
  19. */
  20. to: LocationDescriptor;
  21. query?: string;
  22. // If supplied by parent component, decides whether link element
  23. // is "active" or not ... overriding default behavior of strict
  24. // route matching
  25. isActive?: (location: LocationDescriptor, indexOnly?: boolean) => boolean;
  26. };
  27. class ListLink extends React.Component<Props> {
  28. static displayName = 'ListLink';
  29. static defaultProps: DefaultProps = {
  30. activeClassName: 'active',
  31. index: false,
  32. disabled: false,
  33. };
  34. isActive() {
  35. const {isActive, to, query, index, router} = this.props;
  36. const queryData = query ? qs.parse(query) : undefined;
  37. const target: LocationDescriptor =
  38. typeof to === 'string' ? {pathname: to, query: queryData} : to;
  39. if (typeof isActive === 'function') {
  40. return isActive(target, index);
  41. }
  42. return router.isActive(target, index);
  43. }
  44. getClassName = () => {
  45. const _classNames = {};
  46. const {className, activeClassName} = this.props;
  47. if (className) {
  48. _classNames[className] = true;
  49. }
  50. if (this.isActive() && activeClassName) {
  51. _classNames[activeClassName] = true;
  52. }
  53. return classNames(_classNames);
  54. };
  55. render() {
  56. const {index, children, to, disabled, ...props} = this.props;
  57. const carriedProps = omit(
  58. props,
  59. 'activeClassName',
  60. 'css',
  61. 'isActive',
  62. 'index',
  63. 'router',
  64. 'location'
  65. );
  66. return (
  67. <StyledLi className={this.getClassName()} disabled={disabled}>
  68. <Link {...carriedProps} onlyActiveOnIndex={index} to={disabled ? '' : to}>
  69. {children}
  70. </Link>
  71. </StyledLi>
  72. );
  73. }
  74. }
  75. export default withRouter(ListLink);
  76. const StyledLi = styled('li', {
  77. shouldForwardProp: prop => prop !== 'disabled',
  78. })<{disabled?: boolean}>`
  79. ${p =>
  80. p.disabled &&
  81. `
  82. a {
  83. color:${p.theme.disabled} !important;
  84. pointer-events: none;
  85. :hover {
  86. color: ${p.theme.disabled} !important;
  87. }
  88. }
  89. `}
  90. `;