Use same Link UI component for react-scroll links
This commit is contained in:
100
packages/react-shared/src/components/link.tsx
Normal file
100
packages/react-shared/src/components/link.tsx
Normal 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';
|
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
Reference in New Issue
Block a user