import { DocsInfo } from '@0xproject/react-docs';
import { ALink, colors, constants as sharedConstants, Link, NestedSidebarMenu, Styles } from '@0xproject/react-shared';
import { ObjectMap } from '@0xproject/types';
import * as _ from 'lodash';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { DevelopersDropDown } from 'ts/components/dropdowns/developers_drop_down';
import { DrawerMenu } from 'ts/components/portal/drawer_menu';
import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
import { Container } from 'ts/components/ui/container';
import { Dispatcher } from 'ts/redux/dispatcher';
import { Deco, Key, ProviderType, WebsitePaths } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
export enum TopBarDisplayType {
Default,
Expanded,
}
export interface TopBarProps {
userAddress?: string;
networkId?: number;
injectedProviderName?: string;
providerType?: ProviderType;
onToggleLedgerDialog?: () => void;
blockchain?: Blockchain;
dispatcher?: Dispatcher;
blockchainIsLoaded: boolean;
location: Location;
translate: Translate;
docsVersion?: string;
availableDocVersions?: string[];
sectionNameToLinks?: ObjectMap<ALink[]>;
displayType?: TopBarDisplayType;
docsInfo?: DocsInfo;
style?: React.CSSProperties;
isNightVersion?: boolean;
onVersionSelected?: (semver: string) => void;
sidebarHeader?: React.ReactNode;
maxWidth?: number;
paddingLeft?: number;
paddingRight?: number;
}
interface TopBarState {
isDrawerOpen: boolean;
}
const styles: Styles = {
topBar: {
backgroundColor: colors.white,
width: '100%',
position: 'relative',
top: 0,
paddingBottom: 1,
zIndex: 1,
},
bottomBar: {
boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px',
},
menuItem: {
fontSize: 14,
color: colors.darkestGrey,
paddingTop: 6,
paddingBottom: 6,
cursor: 'pointer',
fontWeight: 400,
},
};
const DOC_WEBSITE_PATHS_TO_KEY = {
[WebsitePaths.SolCov]: Key.SolCov,
[WebsitePaths.SmartContracts]: Key.SmartContracts,
[WebsitePaths.Web3Wrapper]: Key.Web3Wrapper,
[WebsitePaths.SolCompiler]: Key.SolCompiler,
[WebsitePaths.JSONSchemas]: Key.JsonSchemas,
[WebsitePaths.Subproviders]: Key.Subproviders,
[WebsitePaths.ContractWrappers]: Key.ContractWrappers,
[WebsitePaths.Connect]: Key.Connect,
[WebsitePaths.ZeroExJs]: Key.ZeroExJs,
[WebsitePaths.OrderUtils]: Key.OrderUtils,
[WebsitePaths.OrderWatcher]: Key.OrderWatcher,
};
const DEFAULT_HEIGHT = 68;
const EXPANDED_HEIGHT = 75;
export class TopBar extends React.Component<TopBarProps, TopBarState> {
public static defaultProps: Partial<TopBarProps> = {
displayType: TopBarDisplayType.Default,
style: {},
isNightVersion: false,
paddingLeft: 20,
paddingRight: 20,
};
public static heightForDisplayType(displayType: TopBarDisplayType): number {
const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
return result + 1;
}
constructor(props: TopBarProps) {
super(props);
this.state = {
isDrawerOpen: false,
};
}
public componentWillReceiveProps(nextProps: TopBarProps): void {
if (nextProps.location.pathname !== this.props.location.pathname) {
this.setState({
isDrawerOpen: false,
});
}
}
public render(): React.ReactNode {
const isNightVersion = this.props.isNightVersion;
const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded;
const parentClassNames = !isExpandedDisplayType
? 'flex mx-auto items-center max-width-4'
: 'flex mx-auto items-center';
const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
const fullWidthClasses = isExpandedDisplayType ? 'pr4' : '';
const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
const menuClasses = `col col-${
isExpandedDisplayType ? '4' : '6'
} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
const menuIconStyle = {
fontSize: 25,
color: isNightVersion ? 'white' : 'black',
cursor: 'pointer',
};
return (
<div
style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }}
className="pb1 flex items-center"
>
<Container
className={parentClassNames}
width="100%"
maxWidth={this.props.maxWidth}
paddingLeft={this.props.paddingLeft}
paddingRight={this.props.paddingRight}
>
<Link to={WebsitePaths.Home}>
<img src={logoUrl} height="30" />
</Link>
<div className="flex-auto" />
{!this._isViewingPortal() && (
<div className={menuClasses}>
<div className="flex items-center justify-between">
<DevelopersDropDown
location={this.props.location}
menuItemStyles={{ ...styles.menuItem, paddingBottom: 12, paddingTop: 12 }}
translate={this.props.translate}
menuIconStyle={menuIconStyle}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.Wiki, Deco.Cap)}
path={WebsitePaths.Wiki}
style={styles.menuItem}
isNightVersion={isNightVersion}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.Blog, Deco.Cap)}
path={constants.URL_BLOG}
style={styles.menuItem}
isNightVersion={isNightVersion}
shouldOpenInNewTab={true}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.About, Deco.Cap)}
path={WebsitePaths.About}
style={styles.menuItem}
isNightVersion={isNightVersion}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.Careers, Deco.Cap)}
path={WebsitePaths.Careers}
style={styles.menuItem}
isNightVersion={isNightVersion}
/>
<TopBarMenuItem
title={this.props.translate.get(Key.TradeCallToAction, Deco.Cap)}
path={WebsitePaths.Portal}
isPrimary={true}
style={styles.menuItem}
className={`${isExpandedDisplayType && 'md-hide'}`}
isNightVersion={isNightVersion}
/>
</div>
</div>
)}
{this._isViewingPortal() && (
<div className="sm-hide xs-hide">
<ProviderDisplay
dispatcher={this.props.dispatcher}
userAddress={this.props.userAddress}
networkId={this.props.networkId}
injectedProviderName={this.props.injectedProviderName}
providerType={this.props.providerType}
onToggleLedgerDialog={this.props.onToggleLedgerDialog}
blockchain={this.props.blockchain}
blockchainIsLoaded={this.props.blockchainIsLoaded}
/>
</div>
)}
<div className={'md-hide lg-hide'}>
<div style={menuIconStyle}>
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
</div>
</div>
</Container>
{this._isViewingPortal() ? this._renderPortalDrawer() : this._renderDrawer()}
</div>
);
}
private _renderPortalDrawer(): React.ReactNode {
return (
<Drawer
open={this.state.isDrawerOpen}
docked={false}
openSecondary={true}
onRequestChange={this._onMenuButtonClick.bind(this)}
>
<DrawerMenu
selectedPath={this.props.location.pathname}
userAddress={this.props.userAddress}
injectedProviderName={this.props.injectedProviderName}
providerType={this.props.providerType}
blockchainIsLoaded={this.props.blockchainIsLoaded}
blockchain={this.props.blockchain}
/>
</Drawer>
);
}
private _renderDrawer(): React.ReactNode {
return (
<Drawer
open={this.state.isDrawerOpen}
docked={false}
openSecondary={true}
onRequestChange={this._onMenuButtonClick.bind(this)}
>
<div className="clearfix">
{this._renderDocsMenu()}
{this._renderWiki()}
<div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}>
{this.props.translate.get(Key.Website, Deco.Cap)}
</div>
<Link to={WebsitePaths.Home}>
<MenuItem className="py2">{this.props.translate.get(Key.Home, Deco.Cap)}</MenuItem>
</Link>
<Link to={WebsitePaths.Wiki}>
<MenuItem className="py2">{this.props.translate.get(Key.Wiki, Deco.Cap)}</MenuItem>
</Link>
{_.map(DOC_WEBSITE_PATHS_TO_KEY, (key, websitePath) => {
if (!this._doesUrlInclude(websitePath)) {
return (
<Link key={`drawer-menu-item-${websitePath}`} to={websitePath}>
<MenuItem className="py2">
{this.props.translate.get(key, Deco.Cap)}{' '}
{this.props.translate.get(Key.Docs, Deco.Cap)}
</MenuItem>
</Link>
);
}
return null;
})}
{!this._isViewingPortal() && (
<Link to={WebsitePaths.Portal}>
<MenuItem className="py2">
{this.props.translate.get(Key.PortalDApp, Deco.CapWords)}
</MenuItem>
</Link>
)}
<Link to={WebsitePaths.Whitepaper} shouldOpenInNewTab={true}>
<MenuItem className="py2">{this.props.translate.get(Key.Whitepaper, Deco.Cap)}</MenuItem>
</Link>
<Link to={WebsitePaths.About}>
<MenuItem className="py2">{this.props.translate.get(Key.About, Deco.Cap)}</MenuItem>
</Link>
<Link to={WebsitePaths.Careers}>
<MenuItem className="py2">{this.props.translate.get(Key.Careers, Deco.Cap)}</MenuItem>
</Link>
<Link to={constants.URL_BLOG} shouldOpenInNewTab={true}>
<MenuItem className="py2">{this.props.translate.get(Key.Blog, Deco.Cap)}</MenuItem>
</Link>
<Link to={WebsitePaths.FAQ}>
<MenuItem className="py2" onClick={this._onMenuButtonClick.bind(this)}>
{this.props.translate.get(Key.Faq, Deco.Cap)}
</MenuItem>
</Link>
</div>
</Drawer>
);
}
private _renderDocsMenu(): React.ReactNode {
const isViewingDocsPage = _.some(DOC_WEBSITE_PATHS_TO_KEY, (_key, websitePath) => {
return this._doesUrlInclude(websitePath);
});
// HACK: We need to make sure the SCROLL_CONTAINER is loaded before rendering the Sidebar
// because the sidebar renders `react-scroll` links which depend on the scroll container already
// being rendered.
const documentationContainer = document.getElementById(sharedConstants.SCROLL_CONTAINER_ID);
if (!isViewingDocsPage || _.isUndefined(this.props.sectionNameToLinks) || _.isNull(documentationContainer)) {
return undefined;
}
return (
<div className="lg-hide md-hide">
<NestedSidebarMenu
sectionNameToLinks={this.props.sectionNameToLinks}
sidebarHeader={this.props.sidebarHeader}
shouldDisplaySectionHeaders={false}
onMenuItemClick={this._onMenuButtonClick.bind(this)}
selectedVersion={this.props.docsVersion}
versions={this.props.availableDocVersions}
onVersionSelected={this.props.onVersionSelected}
/>
</div>
);
}
private _renderWiki(): React.ReactNode {
if (!this._isViewingWiki()) {
return undefined;
}
return (
<div className="lg-hide md-hide">
<NestedSidebarMenu
sectionNameToLinks={this.props.sectionNameToLinks}
sidebarHeader={this.props.sidebarHeader}
shouldDisplaySectionHeaders={false}
onMenuItemClick={this._onMenuButtonClick.bind(this)}
/>
</div>
);
}
private _onMenuButtonClick(): void {
this.setState({
isDrawerOpen: !this.state.isDrawerOpen,
});
}
private _isViewingPortal(): boolean {
return _.includes(this.props.location.pathname, WebsitePaths.Portal);
}
private _isViewingDocs(): boolean {
return _.includes(this.props.location.pathname, WebsitePaths.Docs);
}
private _isViewingFAQ(): boolean {
return _.includes(this.props.location.pathname, WebsitePaths.FAQ);
}
private _doesUrlInclude(aPath: string): boolean {
return _.includes(this.props.location.pathname, aPath);
}
private _isViewingWiki(): boolean {
return _.includes(this.props.location.pathname, WebsitePaths.Wiki);
}
private _shouldDisplayBottomBar(): boolean {
const isViewingDocsPage = _.some(DOC_WEBSITE_PATHS_TO_KEY, (_key, websitePath) => {
return this._doesUrlInclude(websitePath);
});
return (
isViewingDocsPage ||
this._isViewingWiki() ||
this._isViewingFAQ() ||
this._isViewingDocs() ||
this._isViewingPortal()
);
}
} // tslint:disable:max-file-line-count