You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

97 lines
2.5 KiB

  1. // Inspired by <CommonLink> from Mastodon GO!
  2. // ~ 😘 kibi!
  3. // Package imports.
  4. import classNames from 'classnames';
  5. import PropTypes from 'prop-types';
  6. import React from 'react';
  7. // Utils.
  8. import { assignHandlers } from 'flavours/glitch/util/react_helpers';
  9. // Handlers.
  10. const handlers = {
  11. // We don't handle clicks that are made with modifiers, since these
  12. // often have special browser meanings (eg, "open in new tab").
  13. click (e) {
  14. const { onClick } = this.props;
  15. if (!onClick || e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) {
  16. return;
  17. }
  18. onClick(e);
  19. e.preventDefault(); // Prevents following of the link
  20. },
  21. };
  22. // The component.
  23. export default class Link extends React.PureComponent {
  24. // Constructor.
  25. constructor (props) {
  26. super(props);
  27. assignHandlers(this, handlers);
  28. }
  29. // Rendering.
  30. render () {
  31. const { click } = this.handlers;
  32. const {
  33. children,
  34. className,
  35. href,
  36. onClick,
  37. role,
  38. title,
  39. ...rest
  40. } = this.props;
  41. const computedClass = classNames('link', className, `role-${role}`);
  42. // We assume that our `onClick` is a routing function and give it
  43. // the qualities of a link even if no `href` is provided. However,
  44. // if we have neither an `onClick` or an `href`, our link is
  45. // purely presentational.
  46. const conditionalProps = {};
  47. if (href) {
  48. conditionalProps.href = href;
  49. conditionalProps.onClick = click;
  50. } else if (onClick) {
  51. conditionalProps.onClick = click;
  52. conditionalProps.role = 'link';
  53. conditionalProps.tabIndex = 0;
  54. } else {
  55. conditionalProps.role = 'presentation';
  56. }
  57. // If we were provided a `role` it overwrites any that we may have
  58. // set above. This can be used for "links" which are actually
  59. // buttons.
  60. if (role) {
  61. conditionalProps.role = role;
  62. }
  63. // Rendering. We set `rel='noopener'` for user privacy, and our
  64. // `target` as `'_blank'`.
  65. return (
  66. <a
  67. className={computedClass}
  68. {...conditionalProps}
  69. rel='noopener'
  70. target='_blank'
  71. title={title}
  72. {...rest}
  73. >{children}</a>
  74. );
  75. }
  76. }
  77. // Props.
  78. Link.propTypes = {
  79. children: PropTypes.node,
  80. className: PropTypes.string,
  81. href: PropTypes.string, // The link destination
  82. onClick: PropTypes.func, // A function to call instead of opening the link
  83. role: PropTypes.string, // An ARIA role for the link
  84. title: PropTypes.string, // A title for the link
  85. };