aboutsummaryrefslogtreecommitdiffstats
path: root/packages/react-shared/src/components/link.tsx
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-11-19 20:49:16 +0800
committerFabio Berger <me@fabioberger.com>2018-11-19 20:49:16 +0800
commit8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c (patch)
tree587ce5e026674e5665ad164995aac69462290aed /packages/react-shared/src/components/link.tsx
parent7d2c975d7335155b85a7549c25b953d0afacf5cf (diff)
parent94de441de744ed53470335122a38e265c3a71aff (diff)
downloaddexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar.gz
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar.bz2
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar.lz
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar.xz
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.tar.zst
dexon-0x-contracts-8175192f60e2c2827f6e7d664fbe8bf2d9ddee9c.zip
Merge branch 'development'
* development: (957 commits) fix(order_utils.py): publish docs to S3, not RTD (#1264) fix: make instant package private feat: refer to map file in postpublish configs feat: add new bundle name to bundle watch fix: tslint ignore rule in wrong place Update blog post feature Fix disclaimer on mobile Add smart contract docs to Developer Home Add Apache license link Fix capitalization in title Remove excess semi-colon Point directly to README for docs link Update icons Update LICENSE Fix disclaimer Add blogpost URL Add disclaimer Add launch kit to Developer home list of tools feat: Deploy contracts to Rinkeby fix: fix exceeds block gas limit error ...
Diffstat (limited to 'packages/react-shared/src/components/link.tsx')
-rw-r--r--packages/react-shared/src/components/link.tsx145
1 files changed, 145 insertions, 0 deletions
diff --git a/packages/react-shared/src/components/link.tsx b/packages/react-shared/src/components/link.tsx
new file mode 100644
index 000000000..089e6e2ba
--- /dev/null
+++ b/packages/react-shared/src/components/link.tsx
@@ -0,0 +1,145 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import { Link as ReactRounterLink } from 'react-router-dom';
+import { Link as ScrollLink } from 'react-scroll';
+import * as validUrl from 'valid-url';
+
+import { LinkType } from '../types';
+import { constants } from '../utils/constants';
+
+interface BaseLinkProps {
+ to: string;
+ shouldOpenInNewTab?: boolean;
+ className?: string;
+ onMouseOver?: (event: React.MouseEvent<HTMLElement>) => void;
+ onMouseLeave?: (event: React.MouseEvent<HTMLElement>) => void;
+ onMouseEnter?: (event: React.MouseEvent<HTMLElement>) => void;
+ textDecoration?: string;
+ fontColor?: string;
+}
+
+interface ScrollLinkProps extends BaseLinkProps {
+ onActivityChanged?: (isActive: boolean) => void;
+}
+
+type LinkProps = BaseLinkProps & ScrollLinkProps;
+
+export interface LinkState {}
+
+/**
+ * A generic link component which let's the developer render internal, external and scroll-to-hash links, and
+ * their associated behaviors with a single link component. Many times we want a menu including a combination of
+ * internal, external and scroll links and the abstraction of the differences of rendering each types of link
+ * makes it much easier to do so.
+ */
+export class Link extends React.Component<LinkProps, LinkState> {
+ public static defaultProps: Partial<LinkProps> = {
+ shouldOpenInNewTab: false,
+ className: '',
+ onMouseOver: _.noop.bind(_),
+ onMouseLeave: _.noop.bind(_),
+ onMouseEnter: _.noop.bind(_),
+ textDecoration: 'none',
+ fontColor: 'inherit',
+ };
+ private _outerReactScrollSpan: HTMLSpanElement | null;
+ constructor(props: LinkProps) {
+ super(props);
+ this._outerReactScrollSpan = null;
+ }
+ public render(): React.ReactNode {
+ let type: LinkType;
+ const isReactRoute = _.startsWith(this.props.to, '/');
+ const isExternal = validUrl.isWebUri(this.props.to) || _.startsWith(this.props.to, 'mailto:');
+ if (isReactRoute) {
+ type = LinkType.ReactRoute;
+ } else if (isExternal) {
+ type = LinkType.External;
+ } else {
+ type = LinkType.ReactScroll;
+ }
+
+ if (type === LinkType.ReactScroll && this.props.shouldOpenInNewTab) {
+ throw new Error(`Cannot open LinkType.ReactScroll links in new tab. link.to: ${this.props.to}`);
+ }
+
+ const styleWithDefault = {
+ textDecoration: this.props.textDecoration,
+ cursor: 'pointer',
+ color: this.props.fontColor,
+ };
+
+ switch (type) {
+ case LinkType.External:
+ return (
+ <a
+ target={this.props.shouldOpenInNewTab ? '_blank' : ''}
+ className={this.props.className}
+ style={styleWithDefault}
+ href={this.props.to}
+ onMouseOver={this.props.onMouseOver}
+ onMouseEnter={this.props.onMouseEnter}
+ onMouseLeave={this.props.onMouseLeave}
+ >
+ {this.props.children}
+ </a>
+ );
+ case LinkType.ReactRoute:
+ return (
+ <ReactRounterLink
+ to={this.props.to}
+ className={this.props.className}
+ style={styleWithDefault}
+ target={this.props.shouldOpenInNewTab ? '_blank' : ''}
+ onMouseOver={this.props.onMouseOver}
+ onMouseEnter={this.props.onMouseEnter}
+ onMouseLeave={this.props.onMouseLeave}
+ >
+ {this.props.children}
+ </ReactRounterLink>
+ );
+ case LinkType.ReactScroll:
+ return (
+ <span
+ ref={input => (this._outerReactScrollSpan = input)}
+ onMouseOver={this.props.onMouseOver}
+ onMouseEnter={this.props.onMouseEnter}
+ onMouseLeave={this.props.onMouseLeave}
+ >
+ <ScrollLink
+ to={this.props.to}
+ offset={0}
+ spy={true}
+ hashSpy={true}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.SCROLL_CONTAINER_ID}
+ className={this.props.className}
+ style={styleWithDefault}
+ onSetActive={this._onActivityChanged.bind(this, true)}
+ onSetInactive={this._onActivityChanged.bind(this, false)}
+ >
+ <span onClick={this._onClickPropagateClickEventAroundScrollLink.bind(this)}>
+ {this.props.children}
+ </span>
+ </ScrollLink>
+ </span>
+ );
+ default:
+ throw new Error(`Unrecognized LinkType: ${type}`);
+ }
+ }
+ private _onActivityChanged(isActive: boolean): void {
+ if (this.props.onActivityChanged) {
+ this.props.onActivityChanged(isActive);
+ }
+ }
+ // HACK(fabio): For some reason, the react-scroll link decided to stop the propagation of click events.
+ // We do however rely on these events being propagated in certain scenarios (e.g when the link
+ // is within a dropdown we want to close upon being clicked). Because of this, we register the
+ // click event of an inner span, and pass it around the react-scroll link to an outer span.
+ private _onClickPropagateClickEventAroundScrollLink(): void {
+ if (!_.isNull(this._outerReactScrollSpan)) {
+ this._outerReactScrollSpan.click();
+ }
+ }
+}