diff options
Diffstat (limited to 'packages/react-shared')
-rw-r--r-- | packages/react-shared/package.json | 5 | ||||
-rw-r--r-- | packages/react-shared/src/components/link.tsx | 101 | ||||
-rw-r--r-- | packages/react-shared/src/components/nested_sidebar_menu.tsx | 95 | ||||
-rw-r--r-- | packages/react-shared/src/index.ts | 3 | ||||
-rw-r--r-- | packages/react-shared/src/types.ts | 17 | ||||
-rw-r--r-- | packages/react-shared/src/utils/colors.ts | 7 |
6 files changed, 182 insertions, 46 deletions
diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 95bf849ad..5629ec915 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -34,6 +34,7 @@ "typescript": "3.0.1" }, "dependencies": { + "@0xproject/types": "^1.1.2", "@material-ui/core": "^3.0.1", "@types/is-mobile": "0.3.0", "@types/lodash": "4.14.104", @@ -41,6 +42,7 @@ "@types/node": "*", "@types/react": "*", "@types/react-dom": "*", + "@types/react-router-dom": "^4.0.4", "@types/react-scroll": "1.5.3", "basscss": "^8.0.3", "change-case": "^3.0.2", @@ -51,7 +53,8 @@ "react-dom": "^16.4.2", "react-highlight": "0xproject/react-highlight#2f40a42e0a3f0ad126f9f42d505b97b603fc7162", "react-markdown": "^3.2.2", - "react-scroll": "0xproject/react-scroll#similar-to-pr-330" + "react-scroll": "0xproject/react-scroll#similar-to-pr-330", + "react-router-dom": "^4.1.1" }, "publishConfig": { "access": "public" diff --git a/packages/react-shared/src/components/link.tsx b/packages/react-shared/src/components/link.tsx new file mode 100644 index 000000000..6200bfbd3 --- /dev/null +++ b/packages/react-shared/src/components/link.tsx @@ -0,0 +1,101 @@ +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 and scroll-to-hash links, and + * their associated behaviors with a single link component. Many times we want a menu including a combination of + * internal, external and scroll links and the abstraction of the differences of rendering each types of link + * makes it much easier to do so. + */ +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'; diff --git a/packages/react-shared/src/components/nested_sidebar_menu.tsx b/packages/react-shared/src/components/nested_sidebar_menu.tsx index c8bddb59a..0e6a06a3b 100644 --- a/packages/react-shared/src/components/nested_sidebar_menu.tsx +++ b/packages/react-shared/src/components/nested_sidebar_menu.tsx @@ -1,24 +1,26 @@ +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, 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; selectedVersion?: string; versions?: string[]; onVersionSelected?: (semver: string) => void; + shouldReformatMenuItemNames?: boolean; } export interface NestedSidebarMenuState {} @@ -42,23 +44,24 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N public static defaultProps: Partial<NestedSidebarMenuProps> = { shouldDisplaySectionHeaders: true, onMenuItemClick: _.noop.bind(_), + shouldReformatMenuItemNames: true, + 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 - const id = utils.getIdFromName(sectionName); return ( - <div key={`section-${sectionName}`} className="py1" style={{ color: colors.grey800 }}> - <div style={{ fontWeight: 'bold', fontSize: 15 }} className="py1"> + <div key={`section-${sectionName}`} className="py1" style={{ color: colors.linkSectionGrey }}> + <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; @@ -80,53 +83,63 @@ 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 finalMenuItemName = utils.convertDashesToSpaces(menuItemName); - const id = utils.getIdFromName(menuItemName); + const menuItems = _.map(links, link => { + const finalMenuItemName = this.props.shouldReformatMenuItemNames + ? 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} + <div key={`menuItem-${finalMenuItemName}`}> + <Link + to={link.to} + type={link.type} + shouldOpenInNewTab={link.shouldOpenInNewTab} containerId={constants.DOCS_CONTAINER_ID} > - <MenuItem style={menuItemStyles} innerDivStyle={menuItemInnerDivStyles}> - <span style={{ textTransform: 'capitalize' }}>{finalMenuItemName}</span> + <MenuItem + style={menuItemStyles} + innerDivStyle={menuItemInnerDivStyles} + onClick={this._onMenuItemClick.bind(this)} + > + <span + style={{ + textTransform: this.props.shouldReformatMenuItemNames ? 'capitalize' : 'none', + }} + > + {finalMenuItemName} + </span> </MenuItem> - </ScrollLink> - {this._renderMenuItemSubsections(menuItemName)} + </Link> + {this._renderMenuItemSubsections(link.title)} </div> ); }); return menuItems; } private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { - if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + if ( + _.isUndefined(this.props.subsectionNameToLinks) || + _.isUndefined(this.props.subsectionNameToLinks[menuItemName]) + ) { return null; } - return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + return this._renderSubsectionLinks(menuItemName, this.props.subsectionNameToLinks[menuItemName]); } - private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + private _renderSubsectionLinks(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} + <Link + to={link.to} + type={link.type} + shouldOpenInNewTab={link.shouldOpenInNewTab} containerId={constants.DOCS_CONTAINER_ID} > <MenuItem @@ -136,14 +149,20 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N fontSize: 14, lineHeight: '35px', }} + onClick={this._onMenuItemClick.bind(this)} > - {entityName} + {link.title} </MenuItem> - </ScrollLink> + </Link> </li> ); })} </ul> ); } + private _onMenuItemClick(): void { + if (!_.isUndefined(this.props.onMenuItemClick)) { + this.props.onMenuItemClick(); + } + } } diff --git a/packages/react-shared/src/index.ts b/packages/react-shared/src/index.ts index 3b50c0117..1842e8af3 100644 --- a/packages/react-shared/src/index.ts +++ b/packages/react-shared/src/index.ts @@ -4,8 +4,9 @@ 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 { Link } from './components/link'; -export { HeaderSizes, Styles, MenuSubsectionsBySection, EtherscanLinkSuffixes, Networks } from './types'; +export { HeaderSizes, Styles, EtherscanLinkSuffixes, Networks, LinkType, ALink } from './types'; export { utils } from './utils/utils'; export { constants } from './utils/constants'; diff --git a/packages/react-shared/src/types.ts b/packages/react-shared/src/types.ts index 88fadcc09..daae7b115 100644 --- a/packages/react-shared/src/types.ts +++ b/packages/react-shared/src/types.ts @@ -8,10 +8,6 @@ export enum HeaderSizes { H3 = 'h3', } -export interface MenuSubsectionsBySection { - [section: string]: string[]; -} - export enum EtherscanLinkSuffixes { Address = 'address', Tx = 'tx', @@ -23,3 +19,16 @@ export enum Networks { Ropsten = 'Ropsten', Rinkeby = 'Rinkeby', } + +export enum LinkType { + External = 'EXTERNAL', + ReactScroll = 'REACT_SCROLL', + ReactRoute = 'REACT_ROUTE', +} + +export interface ALink { + title: string; + to: string; + shouldOpenInNewTab?: boolean; + type?: LinkType; +} diff --git a/packages/react-shared/src/utils/colors.ts b/packages/react-shared/src/utils/colors.ts index 5a20eeaa1..596c17e83 100644 --- a/packages/react-shared/src/utils/colors.ts +++ b/packages/react-shared/src/utils/colors.ts @@ -9,7 +9,8 @@ const baseColors = { grey300: '#E0E0E0', beigeWhite: '#E4E4E4', lightBgGrey: '#EDEDED', - grey350: '#cacaca', + grey325: '#dfdfdf', + grey350: '#CACACA', grey400: '#BDBDBD', lightGrey: '#BBBBBB', grey500: '#9E9E9E', @@ -24,10 +25,12 @@ const baseColors = { heroGrey: '#404040', projectsGrey: '#343333', darkestGrey: '#272727', + lightestBlue: '#E7F1FD', lightBlue: '#60A4F4', lightBlueA700: '#0091EA', - linkBlue: '#1D5CDE', + lightLinkBlue: '#3289F1', mediumBlue: '#488AEA', + linkBlue: '#1D5CDE', darkBlue: '#4D5481', lightTurquois: '#aefcdc', turquois: '#058789', |