aboutsummaryrefslogtreecommitdiffstats
path: root/packages/react-shared
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-03-09 15:39:38 +0800
committerFabio Berger <me@fabioberger.com>2018-03-09 15:39:38 +0800
commit9699ee4eff8a6594bd862883cac35de80dfbcf56 (patch)
tree01c5a092bd0180101dcc012e3d21320d9addb073 /packages/react-shared
parentda277f5b2743c666a9a66e4fadf6678edd44fd69 (diff)
parent0eeaac1f2b5095441f7cd04bc948515d600d1f3a (diff)
downloaddexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.gz
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.bz2
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.lz
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.xz
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.zst
dexon-sol-tools-9699ee4eff8a6594bd862883cac35de80dfbcf56.zip
Merge branch 'development' into addPackagePublishConfig
* development: (94 commits) Update CHANGELOG Add solc 0.4.20 and 0.4.21 Prettier sra-report README Add new packages to top level README Updated @0xproject/utils in top level package.json Publish Updated CHANGELOGs Detail tests in the README Add support for ropsten and rinkeby Fix yarn.lock Update list of packages and organize them alphabetically Fix prettier issues Add support for going back to previous hashes via the browser back button to wiki Scroll to previous hashed elements when user clicks back button Add back strict null checks to react-shared package and fix issues remove ability to have implicit dependencies and add missing deps update license remove no-implicit-this Add example & screenshot to npmignore Remove `;` to be nice to windows users ...
Diffstat (limited to 'packages/react-shared')
-rw-r--r--packages/react-shared/.npmignore5
-rw-r--r--packages/react-shared/CHANGELOG.md3
-rw-r--r--packages/react-shared/README.md47
-rw-r--r--packages/react-shared/package.json42
-rw-r--r--packages/react-shared/scripts/postpublish.js5
-rw-r--r--packages/react-shared/src/ts/components/anchor_title.tsx87
-rw-r--r--packages/react-shared/src/ts/components/markdown_code_block.tsx25
-rw-r--r--packages/react-shared/src/ts/components/markdown_link_block.tsx47
-rw-r--r--packages/react-shared/src/ts/components/markdown_section.tsx94
-rw-r--r--packages/react-shared/src/ts/components/nested_sidebar_menu.tsx158
-rw-r--r--packages/react-shared/src/ts/components/section_header.tsx73
-rw-r--r--packages/react-shared/src/ts/components/version_drop_down.tsx39
-rw-r--r--packages/react-shared/src/ts/globals.d.ts7
-rw-r--r--packages/react-shared/src/ts/index.ts12
-rw-r--r--packages/react-shared/src/ts/types.ts25
-rw-r--r--packages/react-shared/src/ts/utils/colors.ts48
-rw-r--r--packages/react-shared/src/ts/utils/constants.ts20
-rw-r--r--packages/react-shared/src/ts/utils/utils.ts45
-rw-r--r--packages/react-shared/tsconfig.json12
-rw-r--r--packages/react-shared/tslint.json8
20 files changed, 802 insertions, 0 deletions
diff --git a/packages/react-shared/.npmignore b/packages/react-shared/.npmignore
new file mode 100644
index 000000000..87bc30436
--- /dev/null
+++ b/packages/react-shared/.npmignore
@@ -0,0 +1,5 @@
+.*
+yarn-error.log
+/src/
+/scripts/
+tsconfig.json
diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md
new file mode 100644
index 000000000..43b92d58a
--- /dev/null
+++ b/packages/react-shared/CHANGELOG.md
@@ -0,0 +1,3 @@
+# CHANGELOG
+
+## v0.0.1 - _March 8, 2018_
diff --git a/packages/react-shared/README.md b/packages/react-shared/README.md
new file mode 100644
index 000000000..da7ff83af
--- /dev/null
+++ b/packages/react-shared/README.md
@@ -0,0 +1,47 @@
+## @0xproject/react-shared
+
+Contains React components & frontend types/utils shared between 0x projects.
+
+## Installation
+
+```bash
+yarn add @0xproject/react-shared
+```
+
+## Contributing
+
+We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install Dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Build
+
+```bash
+yarn build
+```
+
+### Lint
+
+```bash
+yarn lint
+```
+
+### Run Tests
+
+```bash
+yarn test
+```
diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json
new file mode 100644
index 000000000..368e1acc9
--- /dev/null
+++ b/packages/react-shared/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@0xproject/react-shared",
+ "version": "0.0.1",
+ "description": "0x shared react components",
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "scripts": {
+ "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'",
+ "build": "tsc",
+ "build:watch": "tsc -w",
+ "clean": "shx rm -rf lib"
+ },
+ "author": "Fabio Berger",
+ "license": "Apache-2.0",
+ "devDependencies": {
+ "@0xproject/tslint-config": "^0.4.9",
+ "@types/lodash": "^4.14.86",
+ "@types/material-ui": "0.18.0",
+ "@types/node": "^8.0.53",
+ "@types/react": "^15.0.15",
+ "@types/react-dom": "^0.14.23",
+ "@types/react-scroll": "0.0.31",
+ "shx": "^0.2.2",
+ "tslint": "^5.9.1",
+ "typescript": "2.7.1"
+ },
+ "dependencies": {
+ "basscss": "^8.0.3",
+ "is-mobile": "^0.2.2",
+ "lodash": "^4.17.4",
+ "material-ui": "^0.17.1",
+ "react": "15.6.1",
+ "react-dom": "15.6.1",
+ "react-highlight": "0xproject/react-highlight",
+ "react-markdown": "^3.2.2",
+ "react-scroll": "^1.5.2",
+ "react-tap-event-plugin": "^2.0.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/react-shared/scripts/postpublish.js b/packages/react-shared/scripts/postpublish.js
new file mode 100644
index 000000000..639656c7e
--- /dev/null
+++ b/packages/react-shared/scripts/postpublish.js
@@ -0,0 +1,5 @@
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const subPackageName = packageJSON.name;
+postpublish_utils.standardPostPublishAsync(subPackageName);
diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx
new file mode 100644
index 000000000..f44354097
--- /dev/null
+++ b/packages/react-shared/src/ts/components/anchor_title.tsx
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import { Link as ScrollLink } from 'react-scroll';
+
+import { HeaderSizes, Styles } from '../types';
+import { constants } from '../utils/constants';
+import { utils } from '../utils/utils';
+
+const headerSizeToScrollOffset: { [headerSize: string]: number } = {
+ h2: -20,
+ h3: 0,
+};
+
+export interface AnchorTitleProps {
+ title: string | React.ReactNode;
+ id: string;
+ headerSize: HeaderSizes;
+ shouldShowAnchor: boolean;
+}
+
+export interface AnchorTitleState {
+ isHovering: boolean;
+}
+
+const styles: Styles = {
+ anchor: {
+ fontSize: 20,
+ transform: 'rotate(45deg)',
+ cursor: 'pointer',
+ },
+ headers: {
+ WebkitMarginStart: 0,
+ WebkitMarginEnd: 0,
+ fontWeight: 'bold',
+ display: 'block',
+ },
+ h1: {
+ fontSize: '1.8em',
+ },
+ h2: {
+ fontSize: '1.5em',
+ fontWeight: 400,
+ },
+ h3: {
+ fontSize: '1.17em',
+ },
+};
+
+export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> {
+ constructor(props: AnchorTitleProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ let opacity = 0;
+ if (this.props.shouldShowAnchor) {
+ opacity = this.state.isHovering ? 0.6 : 1;
+ }
+ return (
+ <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}>
+ <div className="inline-block" style={{ paddingRight: 4 }}>
+ {this.props.title}
+ </div>
+ <ScrollLink
+ to={this.props.id}
+ offset={headerSizeToScrollOffset[this.props.headerSize]}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <i
+ className="zmdi zmdi-link"
+ onClick={utils.setUrlHash.bind(utils, this.props.id)}
+ style={{ ...styles.anchor, opacity }}
+ onMouseOver={this._setHoverState.bind(this, true)}
+ onMouseOut={this._setHoverState.bind(this, false)}
+ />
+ </ScrollLink>
+ </div>
+ );
+ }
+ private _setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+}
diff --git a/packages/react-shared/src/ts/components/markdown_code_block.tsx b/packages/react-shared/src/ts/components/markdown_code_block.tsx
new file mode 100644
index 000000000..2070bb8e1
--- /dev/null
+++ b/packages/react-shared/src/ts/components/markdown_code_block.tsx
@@ -0,0 +1,25 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as HighLight from 'react-highlight';
+
+export interface MarkdownCodeBlockProps {
+ value: string;
+ language: string;
+}
+
+export interface MarkdownCodeBlockState {}
+
+export class MarkdownCodeBlock extends React.Component<MarkdownCodeBlockProps, MarkdownCodeBlockState> {
+ // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying
+ // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed.
+ public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) {
+ return nextProps.value !== this.props.value || nextProps.language !== this.props.language;
+ }
+ public render() {
+ return (
+ <span style={{ fontSize: 14 }}>
+ <HighLight className={this.props.language || 'javascript'}>{this.props.value}</HighLight>
+ </span>
+ );
+ }
+}
diff --git a/packages/react-shared/src/ts/components/markdown_link_block.tsx b/packages/react-shared/src/ts/components/markdown_link_block.tsx
new file mode 100644
index 000000000..8f5862249
--- /dev/null
+++ b/packages/react-shared/src/ts/components/markdown_link_block.tsx
@@ -0,0 +1,47 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { constants } from '../utils/constants';
+import { utils } from '../utils/utils';
+
+export interface MarkdownLinkBlockProps {
+ href: string;
+}
+
+export interface MarkdownLinkBlockState {}
+
+export class MarkdownLinkBlock extends React.Component<MarkdownLinkBlockProps, MarkdownLinkBlockState> {
+ // Re-rendering a linkBlock causes it to remain unclickable.
+ // We therefore noop re-renders on this component if it's props haven't changed.
+ public shouldComponentUpdate(nextProps: MarkdownLinkBlockProps, nextState: MarkdownLinkBlockState) {
+ return nextProps.href !== this.props.href;
+ }
+ public render() {
+ const href = this.props.href;
+ const isLinkToSection = _.startsWith(href, '#');
+ // If protocol is http or https, we can open in a new tab, otherwise don't for security reasons
+ if (_.startsWith(href, 'http') || _.startsWith(href, 'https')) {
+ return (
+ <a href={href} target="_blank" rel="nofollow noreferrer noopener">
+ {this.props.children}
+ </a>
+ );
+ } else if (isLinkToSection) {
+ return (
+ <a
+ style={{ cursor: 'pointer', textDecoration: 'underline' }}
+ onClick={this._onHashUrlClick.bind(this, href)}
+ >
+ {this.props.children}
+ </a>
+ );
+ } else {
+ return <a href={href}>{this.props.children}</a>;
+ }
+ }
+ private _onHashUrlClick(href: string) {
+ const hash = href.split('#')[1];
+ utils.scrollToHash(hash, constants.SCROLL_CONTAINER_ID);
+ utils.setUrlHash(hash);
+ }
+}
diff --git a/packages/react-shared/src/ts/components/markdown_section.tsx b/packages/react-shared/src/ts/components/markdown_section.tsx
new file mode 100644
index 000000000..d24a43dcb
--- /dev/null
+++ b/packages/react-shared/src/ts/components/markdown_section.tsx
@@ -0,0 +1,94 @@
+import * as _ from 'lodash';
+import RaisedButton from 'material-ui/RaisedButton';
+import * as React from 'react';
+import * as ReactMarkdown from 'react-markdown';
+import { Element as ScrollElement } from 'react-scroll';
+
+import { HeaderSizes } from '../types';
+import { colors } from '../utils/colors';
+import { utils } from '../utils/utils';
+
+import { AnchorTitle } from './anchor_title';
+import { MarkdownCodeBlock } from './markdown_code_block';
+import { MarkdownLinkBlock } from './markdown_link_block';
+
+export interface MarkdownSectionProps {
+ sectionName: string;
+ markdownContent: string;
+ headerSize?: HeaderSizes;
+ githubLink?: string;
+}
+
+interface DefaultMarkdownSectionProps {
+ headerSize: HeaderSizes;
+}
+
+type PropsWithDefaults = MarkdownSectionProps & DefaultMarkdownSectionProps;
+
+export interface MarkdownSectionState {
+ shouldShowAnchor: boolean;
+}
+
+export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> {
+ public static defaultProps: Partial<MarkdownSectionProps> = {
+ headerSize: HeaderSizes.H3,
+ };
+ constructor(props: MarkdownSectionProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const { sectionName, markdownContent, headerSize, githubLink } = this.props as PropsWithDefaults;
+
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div
+ className="md-px1 sm-px2 overflow-hidden"
+ onMouseOver={this._setAnchorVisibility.bind(this, true)}
+ onMouseOut={this._setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <div className="clearfix pt3">
+ <div className="col lg-col-8 md-col-8 sm-col-12">
+ <span style={{ textTransform: 'capitalize', color: colors.grey700 }}>
+ <AnchorTitle
+ headerSize={headerSize}
+ title={sectionName}
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </span>
+ </div>
+ <div className="col col-4 sm-hide xs-hide right-align pr3" style={{ height: 28 }}>
+ {!_.isUndefined(githubLink) && (
+ <a
+ href={githubLink}
+ target="_blank"
+ style={{ color: colors.linkBlue, textDecoration: 'none', lineHeight: 2.1 }}
+ >
+ Edit on Github
+ </a>
+ )}
+ </div>
+ </div>
+ <hr style={{ border: `1px solid ${colors.lightestGrey}` }} />
+ <ReactMarkdown
+ source={markdownContent}
+ escapeHtml={false}
+ renderers={{
+ code: MarkdownCodeBlock,
+ link: MarkdownLinkBlock,
+ }}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private _setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx
new file mode 100644
index 000000000..2225bd197
--- /dev/null
+++ b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx
@@ -0,0 +1,158 @@
+import * as _ from 'lodash';
+import MenuItem from 'material-ui/MenuItem';
+import * as React from 'react';
+import { Link as ScrollLink } from 'react-scroll';
+
+import { MenuSubsectionsBySection, Styles } from '../types';
+import { colors } from '../utils/colors';
+import { constants } from '../utils/constants';
+import { utils } from '../utils/utils';
+
+import { VersionDropDown } from './version_drop_down';
+
+export interface NestedSidebarMenuProps {
+ topLevelMenu: { [topLevel: string]: string[] };
+ menuSubsectionsBySection: MenuSubsectionsBySection;
+ sidebarHeader?: React.ReactNode;
+ shouldDisplaySectionHeaders?: boolean;
+ onMenuItemClick?: () => void;
+ selectedVersion?: string;
+ versions?: string[];
+ onVersionSelected?: (semver: string) => void;
+}
+
+export interface NestedSidebarMenuState {}
+
+const styles: Styles = {
+ menuItemWithHeaders: {
+ minHeight: 0,
+ },
+ menuItemWithoutHeaders: {
+ minHeight: 48,
+ },
+ menuItemInnerDivWithHeaders: {
+ color: colors.grey800,
+ fontSize: 14,
+ lineHeight: 2,
+ padding: 0,
+ },
+};
+
+export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> {
+ public static defaultProps: Partial<NestedSidebarMenuProps> = {
+ shouldDisplaySectionHeaders: true,
+ onMenuItemClick: _.noop,
+ };
+ public render() {
+ const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => {
+ const finalSectionName = sectionName.replace(/-/g, ' ');
+ if (this.props.shouldDisplaySectionHeaders) {
+ const id = utils.getIdFromName(sectionName);
+ return (
+ <div key={`section-${sectionName}`} className="py1" style={{ color: colors.grey800 }}>
+ <div style={{ fontWeight: 'bold', fontSize: 15 }} className="py1">
+ {finalSectionName.toUpperCase()}
+ </div>
+ {this._renderMenuItems(menuItems)}
+ </div>
+ );
+ } else {
+ return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>;
+ }
+ });
+ const maxWidthWithScrollbar = 307;
+ return (
+ <div>
+ {this.props.sidebarHeader}
+ {!_.isUndefined(this.props.versions) &&
+ !_.isUndefined(this.props.selectedVersion) &&
+ !_.isUndefined(this.props.onVersionSelected) && (
+ <div style={{ maxWidth: maxWidthWithScrollbar }}>
+ <VersionDropDown
+ selectedVersion={this.props.selectedVersion}
+ versions={this.props.versions}
+ onVersionSelected={this.props.onVersionSelected}
+ />
+ </div>
+ )}
+ <div className="pl1">{navigation}</div>
+ </div>
+ );
+ }
+ private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] {
+ const menuItemStyles = this.props.shouldDisplaySectionHeaders
+ ? styles.menuItemWithHeaders
+ : styles.menuItemWithoutHeaders;
+ const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {};
+ const menuItems = _.map(menuItemNames, menuItemName => {
+ const id = utils.getIdFromName(menuItemName);
+ return (
+ <div key={menuItemName}>
+ <ScrollLink
+ key={`menuItem-${menuItemName}`}
+ to={id}
+ offset={-10}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ >
+ <MenuItem
+ onTouchTap={this._onMenuItemClick.bind(this, menuItemName)}
+ style={menuItemStyles}
+ innerDivStyle={menuItemInnerDivStyles}
+ >
+ <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span>
+ </MenuItem>
+ </ScrollLink>
+ {this._renderMenuItemSubsections(menuItemName)}
+ </div>
+ );
+ });
+ return menuItems;
+ }
+ private _renderMenuItemSubsections(menuItemName: string): React.ReactNode {
+ if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) {
+ return null;
+ }
+ return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]);
+ }
+ private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode {
+ return (
+ <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}>
+ {_.map(entityNames, entityName => {
+ const name = `${menuItemName}-${entityName}`;
+ const id = utils.getIdFromName(name);
+ return (
+ <li key={`menuItem-${entityName}`}>
+ <ScrollLink
+ to={id}
+ offset={0}
+ duration={constants.DOCS_SCROLL_DURATION_MS}
+ containerId={constants.DOCS_CONTAINER_ID}
+ onTouchTap={this._onMenuItemClick.bind(this, name)}
+ >
+ <MenuItem
+ onTouchTap={this._onMenuItemClick.bind(this, name)}
+ style={{ minHeight: 35 }}
+ innerDivStyle={{
+ paddingLeft: 16,
+ fontSize: 14,
+ lineHeight: '35px',
+ }}
+ >
+ {entityName}
+ </MenuItem>
+ </ScrollLink>
+ </li>
+ );
+ })}
+ </ul>
+ );
+ }
+ private _onMenuItemClick(name: string): void {
+ const id = utils.getIdFromName(name);
+ utils.setUrlHash(id);
+ if (!_.isUndefined(this.props.onMenuItemClick)) {
+ this.props.onMenuItemClick();
+ }
+ }
+}
diff --git a/packages/react-shared/src/ts/components/section_header.tsx b/packages/react-shared/src/ts/components/section_header.tsx
new file mode 100644
index 000000000..ee34a6c09
--- /dev/null
+++ b/packages/react-shared/src/ts/components/section_header.tsx
@@ -0,0 +1,73 @@
+import * as React from 'react';
+import { Element as ScrollElement } from 'react-scroll';
+
+import { HeaderSizes } from '../types';
+import { colors } from '../utils/colors';
+import { utils } from '../utils/utils';
+
+import { AnchorTitle } from './anchor_title';
+
+export interface SectionHeaderProps {
+ sectionName: string;
+ headerSize?: HeaderSizes;
+}
+
+interface DefaultSectionHeaderProps {
+ headerSize: HeaderSizes;
+}
+
+type PropsWithDefaults = SectionHeaderProps & DefaultSectionHeaderProps;
+
+export interface SectionHeaderState {
+ shouldShowAnchor: boolean;
+}
+
+export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> {
+ public static defaultProps: Partial<SectionHeaderProps> = {
+ headerSize: HeaderSizes.H2,
+ };
+ constructor(props: SectionHeaderProps) {
+ super(props);
+ this.state = {
+ shouldShowAnchor: false,
+ };
+ }
+ public render() {
+ const { sectionName, headerSize } = this.props as PropsWithDefaults;
+
+ const finalSectionName = this.props.sectionName.replace(/-/g, ' ');
+ const id = utils.getIdFromName(finalSectionName);
+ return (
+ <div
+ onMouseOver={this._setAnchorVisibility.bind(this, true)}
+ onMouseOut={this._setAnchorVisibility.bind(this, false)}
+ >
+ <ScrollElement name={id}>
+ <AnchorTitle
+ headerSize={headerSize}
+ title={
+ <span
+ style={{
+ textTransform: 'uppercase',
+ color: colors.grey,
+ fontFamily: 'Roboto Mono',
+ fontWeight: 300,
+ fontSize: 27,
+ }}
+ >
+ {finalSectionName}
+ </span>
+ }
+ id={id}
+ shouldShowAnchor={this.state.shouldShowAnchor}
+ />
+ </ScrollElement>
+ </div>
+ );
+ }
+ private _setAnchorVisibility(shouldShowAnchor: boolean) {
+ this.setState({
+ shouldShowAnchor,
+ });
+ }
+}
diff --git a/packages/react-shared/src/ts/components/version_drop_down.tsx b/packages/react-shared/src/ts/components/version_drop_down.tsx
new file mode 100644
index 000000000..d9e49b205
--- /dev/null
+++ b/packages/react-shared/src/ts/components/version_drop_down.tsx
@@ -0,0 +1,39 @@
+import * as _ from 'lodash';
+import DropDownMenu from 'material-ui/DropDownMenu';
+import MenuItem from 'material-ui/MenuItem';
+import * as React from 'react';
+
+import { utils } from '../utils/utils';
+
+export interface VersionDropDownProps {
+ selectedVersion: string;
+ versions: string[];
+ onVersionSelected: (semver: string) => void;
+}
+
+export interface VersionDropDownState {}
+
+export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> {
+ public render() {
+ return (
+ <div className="mx-auto" style={{ width: 120 }}>
+ <DropDownMenu
+ maxHeight={300}
+ value={this.props.selectedVersion}
+ onChange={this._updateSelectedVersion.bind(this)}
+ >
+ {this._renderDropDownItems()}
+ </DropDownMenu>
+ </div>
+ );
+ }
+ private _renderDropDownItems() {
+ const items = _.map(this.props.versions, version => {
+ return <MenuItem key={version} value={version} primaryText={`v${version}`} />;
+ });
+ return items;
+ }
+ private _updateSelectedVersion(e: any, index: number, semver: string) {
+ this.props.onVersionSelected(semver);
+ }
+}
diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts
new file mode 100644
index 000000000..9b0bcf845
--- /dev/null
+++ b/packages/react-shared/src/ts/globals.d.ts
@@ -0,0 +1,7 @@
+declare module 'react-highlight';
+
+// is-mobile declarations
+declare function isMobile(): boolean;
+declare module 'is-mobile' {
+ export = isMobile;
+}
diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts
new file mode 100644
index 000000000..3b50c0117
--- /dev/null
+++ b/packages/react-shared/src/ts/index.ts
@@ -0,0 +1,12 @@
+export { AnchorTitle } from './components/anchor_title';
+export { MarkdownLinkBlock } from './components/markdown_link_block';
+export { MarkdownCodeBlock } from './components/markdown_code_block';
+export { MarkdownSection } from './components/markdown_section';
+export { NestedSidebarMenu } from './components/nested_sidebar_menu';
+export { SectionHeader } from './components/section_header';
+
+export { HeaderSizes, Styles, MenuSubsectionsBySection, EtherscanLinkSuffixes, Networks } from './types';
+
+export { utils } from './utils/utils';
+export { constants } from './utils/constants';
+export { colors } from './utils/colors';
diff --git a/packages/react-shared/src/ts/types.ts b/packages/react-shared/src/ts/types.ts
new file mode 100644
index 000000000..88fadcc09
--- /dev/null
+++ b/packages/react-shared/src/ts/types.ts
@@ -0,0 +1,25 @@
+export interface Styles {
+ [name: string]: React.CSSProperties;
+}
+
+export enum HeaderSizes {
+ H1 = 'h1',
+ H2 = 'h2',
+ H3 = 'h3',
+}
+
+export interface MenuSubsectionsBySection {
+ [section: string]: string[];
+}
+
+export enum EtherscanLinkSuffixes {
+ Address = 'address',
+ Tx = 'tx',
+}
+
+export enum Networks {
+ Mainnet = 'Mainnet',
+ Kovan = 'Kovan',
+ Ropsten = 'Ropsten',
+ Rinkeby = 'Rinkeby',
+}
diff --git a/packages/react-shared/src/ts/utils/colors.ts b/packages/react-shared/src/ts/utils/colors.ts
new file mode 100644
index 000000000..2eead95c7
--- /dev/null
+++ b/packages/react-shared/src/ts/utils/colors.ts
@@ -0,0 +1,48 @@
+import { colors as materialUiColors } from 'material-ui/styles';
+
+export const colors = {
+ ...materialUiColors,
+ gray40: '#F8F8F8',
+ grey50: '#FAFAFA',
+ grey100: '#F5F5F5',
+ lightestGrey: '#F0F0F0',
+ greyishPink: '#E6E5E5',
+ grey300: '#E0E0E0',
+ beigeWhite: '#E4E4E4',
+ grey350: '#cacaca',
+ grey400: '#BDBDBD',
+ lightGrey: '#BBBBBB',
+ grey500: '#9E9E9E',
+ grey: '#A5A5A5',
+ darkGrey: '#818181',
+ landingLinkGrey: '#919191',
+ grey700: '#616161',
+ grey750: '#515151',
+ grey800: '#424242',
+ darkerGrey: '#393939',
+ heroGrey: '#404040',
+ projectsGrey: '#343333',
+ darkestGrey: '#272727',
+ dharmaDarkGrey: '#252525',
+ lightBlue: '#60A4F4',
+ lightBlueA700: '#0091EA',
+ linkBlue: '#1D5CDE',
+ darkBlue: '#4D5481',
+ turquois: '#058789',
+ lightPurple: '#A81CA6',
+ purple: '#690596',
+ red200: '#EF9A9A',
+ red: '#E91751',
+ red500: '#F44336',
+ red600: '#E53935',
+ limeGreen: '#66DE75',
+ lightGreen: '#4DC55C',
+ lightestGreen: '#89C774',
+ brightGreen: '#00C33E',
+ green400: '#66BB6A',
+ green: '#4DA24B',
+ amber600: '#FFB300',
+ orange: '#E69D00',
+ amber800: '#FF8F00',
+ darkYellow: '#caca03',
+};
diff --git a/packages/react-shared/src/ts/utils/constants.ts b/packages/react-shared/src/ts/utils/constants.ts
new file mode 100644
index 000000000..562ab776b
--- /dev/null
+++ b/packages/react-shared/src/ts/utils/constants.ts
@@ -0,0 +1,20 @@
+import { Networks } from '../types';
+
+export const constants = {
+ DOCS_SCROLL_DURATION_MS: 0,
+ DOCS_CONTAINER_ID: 'documentation',
+ SCROLL_CONTAINER_ID: 'documentation',
+ SCROLL_TOP_ID: 'pageScrollTop',
+ NETWORK_NAME_BY_ID: {
+ 1: Networks.Mainnet,
+ 3: Networks.Ropsten,
+ 4: Networks.Rinkeby,
+ 42: Networks.Kovan,
+ } as { [symbol: number]: string },
+ NETWORK_ID_BY_NAME: {
+ [Networks.Mainnet]: 1,
+ [Networks.Ropsten]: 3,
+ [Networks.Rinkeby]: 4,
+ [Networks.Kovan]: 42,
+ } as { [networkName: string]: number },
+};
diff --git a/packages/react-shared/src/ts/utils/utils.ts b/packages/react-shared/src/ts/utils/utils.ts
new file mode 100644
index 000000000..b3acb081e
--- /dev/null
+++ b/packages/react-shared/src/ts/utils/utils.ts
@@ -0,0 +1,45 @@
+import isMobile = require('is-mobile');
+import * as _ from 'lodash';
+import { scroller } from 'react-scroll';
+
+import { EtherscanLinkSuffixes, Networks } from '../types';
+
+import { constants } from './constants';
+
+export const utils = {
+ setUrlHash(anchorId: string) {
+ window.location.hash = anchorId;
+ },
+ scrollToHash(hash: string, containerId: string): void {
+ let finalHash = hash;
+ if (_.isEmpty(hash)) {
+ finalHash = constants.SCROLL_TOP_ID; // scroll to the top
+ }
+
+ scroller.scrollTo(finalHash, {
+ duration: 0,
+ offset: 0,
+ containerId,
+ });
+ },
+ isUserOnMobile(): boolean {
+ const isUserOnMobile = isMobile();
+ return isUserOnMobile;
+ },
+ getIdFromName(name: string) {
+ const id = name.replace(/ /g, '-');
+ return id;
+ },
+ getEtherScanLinkIfExists(
+ addressOrTxHash: string,
+ networkId: number,
+ suffix: EtherscanLinkSuffixes,
+ ): string | undefined {
+ const networkName = constants.NETWORK_NAME_BY_ID[networkId];
+ if (_.isUndefined(networkName)) {
+ return undefined;
+ }
+ const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`;
+ return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`;
+ },
+};
diff --git a/packages/react-shared/tsconfig.json b/packages/react-shared/tsconfig.json
new file mode 100644
index 000000000..de87aa45b
--- /dev/null
+++ b/packages/react-shared/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "./lib/",
+ "jsx": "react",
+ "baseUrl": "./",
+ "paths": {
+ "*": ["node_modules/@types/*", "*"]
+ }
+ },
+ "include": ["./src/ts/**/*"]
+}
diff --git a/packages/react-shared/tslint.json b/packages/react-shared/tslint.json
new file mode 100644
index 000000000..ee918e360
--- /dev/null
+++ b/packages/react-shared/tslint.json
@@ -0,0 +1,8 @@
+{
+ "extends": ["@0xproject/tslint-config"],
+ "rules": {
+ "no-object-literal-type-assertion": false,
+ "completed-docs": false,
+ "prefer-function-over-method": false
+ }
+}