fbredius/storybook

View on GitHub
addons/links/src/react/components/link.tsx

Summary

Maintainability
A
45 mins
Test Coverage
import React, { MouseEvent, PureComponent, ReactNode } from 'react';

import { navigate, hrefTo } from '../../preview';

// FIXME: copied from Typography.Link. Code is duplicated to
// avoid emotion dependency which breaks React 15.x back-compat

// Cmd/Ctrl/Shift/Alt + Click should trigger default browser behaviour. Same applies to non-left clicks
const LEFT_BUTTON = 0;

const isPlainLeftClick = (e: MouseEvent<HTMLAnchorElement>) =>
  e.button === LEFT_BUTTON && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey;

const cancelled = (e: MouseEvent<HTMLAnchorElement>, cb = (_e: any) => {}) => {
  if (isPlainLeftClick(e)) {
    e.preventDefault();
    cb(e);
  }
};

interface Props {
  kind: string;
  story: string;
  children: ReactNode;
}

interface State {
  href: string;
}

export default class LinkTo extends PureComponent<Props, State> {
  static defaultProps: Props = {
    kind: null,
    story: null,
    children: undefined,
  };

  state: State = {
    href: '/',
  };

  componentDidMount() {
    this.updateHref();
  }

  componentDidUpdate(prevProps: Props) {
    const { kind, story } = this.props;

    if (prevProps.kind !== kind || prevProps.story !== story) {
      this.updateHref();
    }
  }

  updateHref = async () => {
    const { kind, story } = this.props;
    const href = await hrefTo(kind, story);
    this.setState({ href });
  };

  handleClick = () => {
    navigate(this.props);
  };

  render() {
    const { kind, story, children, ...rest } = this.props;
    const { href } = this.state;

    return (
      <a href={href} onClick={(e) => cancelled(e, this.handleClick)} {...rest}>
        {children}
      </a>
    );
  }
}