Use same Link UI component for react-scroll links

This commit is contained in:
Fabio Berger
2018-10-03 16:37:59 +01:00
parent ab855cdd1c
commit 437612f8b8
28 changed files with 212 additions and 136 deletions

View File

@@ -0,0 +1,100 @@
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 { LinkType } from '../types';
import { constants } from '../utils/constants';
export interface LinkProps {
to: string;
type?: LinkType;
shouldOpenInNewTab?: boolean;
style?: React.CSSProperties;
className?: string;
onMouseOver?: (event: React.MouseEvent<HTMLElement>) => void;
onMouseLeave?: (event: React.MouseEvent<HTMLElement>) => void;
onMouseEnter?: (event: React.MouseEvent<HTMLElement>) => void;
containerId?: string;
}
/**
* A generic link component which let's the developer render internal & external links, and their associated
* behaviors with a single link component. Many times we want a menu including both internal & external links
* and this abstracts away the differences of rendering both types of links.
*/
export const Link: React.StatelessComponent<LinkProps> = ({
style,
className,
type,
to,
shouldOpenInNewTab,
children,
onMouseOver,
onMouseLeave,
onMouseEnter,
containerId,
}) => {
const styleWithDefault = {
textDecoration: 'none',
...style,
};
switch (type) {
case LinkType.External:
return (
<a
target={shouldOpenInNewTab ? '_blank' : ''}
className={className}
style={styleWithDefault}
href={to}
onMouseOver={onMouseOver}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
case LinkType.ReactRoute:
return (
<ReactRounterLink
to={to}
className={className}
style={styleWithDefault}
target={shouldOpenInNewTab ? '_blank' : ''}
onMouseOver={onMouseOver}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</ReactRounterLink>
);
case LinkType.ReactScroll:
return (
<ScrollLink
to={to}
offset={0}
hashSpy={true}
duration={constants.DOCS_SCROLL_DURATION_MS}
containerId={containerId}
>
{children}
</ScrollLink>
);
default:
throw new Error(`Unrecognized LinkType: ${type}`);
}
};
Link.defaultProps = {
type: LinkType.ReactRoute,
shouldOpenInNewTab: false,
style: {},
className: '',
onMouseOver: _.noop.bind(_),
onMouseLeave: _.noop.bind(_),
onMouseEnter: _.noop.bind(_),
containerId: constants.DOCS_CONTAINER_ID,
};
Link.displayName = 'Link';

View File

@@ -1,18 +1,19 @@
import { ObjectMap } from '@0xproject/types';
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 { ALink, LinkType, Styles } from '../types';
import { colors } from '../utils/colors';
import { constants } from '../utils/constants';
import { utils } from '../utils/utils';
import { Link } from './Link';
import { VersionDropDown } from './version_drop_down';
export interface NestedSidebarMenuProps {
topLevelMenu: { [topLevel: string]: string[] };
menuSubsectionsBySection?: MenuSubsectionsBySection;
sectionNameToLinks: ObjectMap<ALink[]>;
subsectionNameToLinks?: ObjectMap<ALink[]>;
sidebarHeader?: React.ReactNode;
shouldDisplaySectionHeaders?: boolean;
onMenuItemClick?: () => void;
@@ -44,10 +45,10 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
shouldDisplaySectionHeaders: true,
onMenuItemClick: _.noop.bind(_),
shouldReformatMenuItemNames: true,
menuSubsectionsBySection: {},
subsectionNameToLinks: {},
};
public render(): React.ReactNode {
const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => {
const navigation = _.map(this.props.sectionNameToLinks, (links: ALink[], sectionName: string) => {
const finalSectionName = utils.convertCamelCaseToSpaces(sectionName);
if (this.props.shouldDisplaySectionHeaders) {
// tslint:disable-next-line:no-unused-variable
@@ -56,11 +57,11 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
<div style={{ fontWeight: 'bold', fontSize: 15, letterSpacing: 0.5 }} className="py1">
{finalSectionName.toUpperCase()}
</div>
{this._renderMenuItems(menuItems)}
{this._renderMenuItems(links)}
</div>
);
} else {
return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>;
return <div key={`section-${sectionName}`}>{this._renderMenuItems(links)}</div>;
}
});
const maxWidthWithScrollbar = 307;
@@ -82,26 +83,18 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
</div>
);
}
private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] {
private _renderMenuItems(links: ALink[]): React.ReactNode[] {
const menuItemStyles = this.props.shouldDisplaySectionHeaders
? styles.menuItemWithHeaders
: styles.menuItemWithoutHeaders;
const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {};
const menuItems = _.map(menuItemNames, menuItemName => {
const menuItems = _.map(links, link => {
const finalMenuItemName = this.props.shouldReformatMenuItemNames
? utils.convertDashesToSpaces(menuItemName)
: menuItemName;
const id = utils.getIdFromName(menuItemName);
? utils.convertDashesToSpaces(link.title)
: link.title;
return (
<div key={menuItemName}>
<ScrollLink
key={`menuItem-${menuItemName}`}
to={id}
offset={0}
hashSpy={true}
duration={constants.DOCS_SCROLL_DURATION_MS}
containerId={constants.DOCS_CONTAINER_ID}
>
<div key={`menuItem-${finalMenuItemName}`}>
<Link to={link.to} type={LinkType.ReactScroll} containerId={constants.DOCS_CONTAINER_ID}>
<MenuItem
style={menuItemStyles}
innerDivStyle={menuItemInnerDivStyles}
@@ -115,8 +108,8 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
{finalMenuItemName}
</span>
</MenuItem>
</ScrollLink>
{this._renderMenuItemSubsections(menuItemName)}
</Link>
{this._renderMenuItemSubsections(link.title)}
</div>
);
});
@@ -124,28 +117,21 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
}
private _renderMenuItemSubsections(menuItemName: string): React.ReactNode {
if (
_.isUndefined(this.props.menuSubsectionsBySection) ||
_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])
_.isUndefined(this.props.subsectionNameToLinks) ||
_.isUndefined(this.props.subsectionNameToLinks[menuItemName])
) {
return null;
}
return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]);
return this._renderMenuSubsectionsBySection(menuItemName, this.props.subsectionNameToLinks[menuItemName]);
}
private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode {
private _renderMenuSubsectionsBySection(menuItemName: string, links: ALink[]): 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);
{_.map(links, link => {
const name = `${menuItemName}-${link.title}`;
return (
<li key={`menuSubsectionItem-${name}`}>
<ScrollLink
to={id}
offset={0}
hashSpy={true}
duration={constants.DOCS_SCROLL_DURATION_MS}
containerId={constants.DOCS_CONTAINER_ID}
>
<Link to={link.to} type={LinkType.ReactScroll} containerId={constants.DOCS_CONTAINER_ID}>
<MenuItem
style={{ minHeight: 35 }}
innerDivStyle={{
@@ -155,9 +141,9 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N
}}
onClick={this._onMenuItemClick.bind(this)}
>
{entityName}
{link.title}
</MenuItem>
</ScrollLink>
</Link>
</li>
);
})}