aboutsummaryrefslogtreecommitdiffstats
path: root/packages/react-shared/src
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-03-09 00:43:16 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-03-09 00:43:16 +0800
commit098dae9a7e57385505f0f272fd76d44f43fa1e34 (patch)
treebcd47532910e5804746ec88c8d9f6b85c157d6e1 /packages/react-shared/src
parent5b5037a844f49c17deeaf695fc8ed57832038da6 (diff)
parentaaa7affa46450bb48639e9b90c2a2e8adb28826c (diff)
downloaddexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.gz
dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.zst
dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.zip
Merge branch 'development' into feature/sra-reporter
* development: (68 commits) 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 Use unencoded @ symbol, browser will fix Fix external type links Add comment about commented out CSS exception Update prettier since the previous version had a bug when dealing with css files Fix css files with prettier Added base-contract package to README Prettify test jsons Update yarn.lock Improve README Feedback ...
Diffstat (limited to 'packages/react-shared/src')
-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
13 files changed, 680 insertions, 0 deletions
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}`;
+ },
+};