From 5a90fece8020f9be5c0f52f6ccf65dacb824b1cd Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 5 Mar 2018 13:53:13 +0100 Subject: Moved over all pages/shared components and dependencies to react-shared --- packages/react-shared/package.json | 4 +- .../src/ts/components/anchor_title.tsx | 4 +- .../src/ts/components/markdown_code_block.tsx | 25 +++ .../src/ts/components/markdown_link_block.tsx | 47 ++++++ .../src/ts/components/markdown_section.tsx | 87 ++++++++++ .../src/ts/components/nested_sidebar_menu.tsx | 179 +++++++++++++++++++++ .../src/ts/components/section_header.tsx | 65 ++++++++ .../src/ts/components/version_drop_down.tsx | 46 ++++++ packages/react-shared/src/ts/constants.ts | 4 - packages/react-shared/src/ts/globals.d.ts | 1 + packages/react-shared/src/ts/index.ts | 12 +- packages/react-shared/src/ts/types.ts | 4 + packages/react-shared/src/ts/utils.ts | 5 - packages/react-shared/src/ts/utils/colors.ts | 48 ++++++ packages/react-shared/src/ts/utils/constants.ts | 6 + packages/react-shared/src/ts/utils/utils.ts | 32 ++++ 16 files changed, 554 insertions(+), 15 deletions(-) create mode 100644 packages/react-shared/src/ts/components/markdown_code_block.tsx create mode 100644 packages/react-shared/src/ts/components/markdown_link_block.tsx create mode 100644 packages/react-shared/src/ts/components/markdown_section.tsx create mode 100644 packages/react-shared/src/ts/components/nested_sidebar_menu.tsx create mode 100644 packages/react-shared/src/ts/components/section_header.tsx create mode 100644 packages/react-shared/src/ts/components/version_drop_down.tsx delete mode 100644 packages/react-shared/src/ts/constants.ts delete mode 100644 packages/react-shared/src/ts/utils.ts create mode 100644 packages/react-shared/src/ts/utils/colors.ts create mode 100644 packages/react-shared/src/ts/utils/constants.ts create mode 100644 packages/react-shared/src/ts/utils/utils.ts (limited to 'packages/react-shared') diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index e1cc2ad8b..e96659e47 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -7,6 +7,7 @@ "scripts": { "lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'", "build": "tsc", + "build:watch": "tsc -w", "clean": "shx rm -rf lib" }, "author": "Fabio Berger", @@ -29,6 +30,7 @@ "react-dom": "15.6.1", "lodash": "^4.17.4", "react-scroll": "^1.5.2", - "react-tap-event-plugin": "^2.0.1" + "react-tap-event-plugin": "^2.0.1", + "react-highlight": "0xproject/react-highlight" } } diff --git a/packages/react-shared/src/ts/components/anchor_title.tsx b/packages/react-shared/src/ts/components/anchor_title.tsx index aa839cac9..f44354097 100644 --- a/packages/react-shared/src/ts/components/anchor_title.tsx +++ b/packages/react-shared/src/ts/components/anchor_title.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { Link as ScrollLink } from 'react-scroll'; -import { constants } from '../constants'; import { HeaderSizes, Styles } from '../types'; -import { utils } from '../utils'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; const headerSizeToScrollOffset: { [headerSize: string]: number } = { h2: -20, 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 { + // 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 ( + + {this.props.value} + + ); + } +} 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 { + // 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 ( + + {this.props.children} + + ); + } else if (isLinkToSection) { + return ( + + {this.props.children} + + ); + } else { + return {this.props.children}; + } + } + 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..682b6ef8f --- /dev/null +++ b/packages/react-shared/src/ts/components/markdown_section.tsx @@ -0,0 +1,87 @@ +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; +} + +export interface MarkdownSectionState { + shouldShowAnchor: boolean; +} + +export class MarkdownSection extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName; + const id = utils.getIdFromName(sectionName); + return ( +
+ +
+
+ + + +
+
+ {!_.isUndefined(this.props.githubLink) && ( + + Edit on Github + + )} +
+
+
+ +
+
+ ); + } + 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..2506124af --- /dev/null +++ b/packages/react-shared/src/ts/components/nested_sidebar_menu.tsx @@ -0,0 +1,179 @@ +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; + title: string; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; +} + +export interface NestedSidebarMenuState {} + +const styles: Styles = { + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + color: colors.grey800, + fontSize: 14, + lineHeight: 2, + padding: 0, + }, +}; + +const titleToIcon: { [title: string]: string } = { + '0x.js': 'zeroExJs.png', + '0x Connect': 'connect.png', + '0x Smart Contracts': 'contracts.png', + Wiki: 'wiki.png', +}; + +export class NestedSidebarMenu extends React.Component { + public static defaultProps: Partial = { + 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 ( +
+
+ {finalSectionName.toUpperCase()} +
+ {this._renderMenuItems(menuItems)} +
+ ); + } else { + return
{this._renderMenuItems(menuItems)}
; + } + }); + return ( +
+ {this._renderEmblem()} + {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && ( + + )} +
{navigation}
+
+ ); + } + private _renderEmblem() { + return ( +
+
+
+ 0x +
+
+ docs +
+
+
+ | +
+
+
+ +
+
+ {this.props.title} +
+
+
+ ); + } + 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 ( +
+ + + {menuItemName} + + + {this._renderMenuItemSubsections(menuItemName)} +
+ ); + }); + 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 ( +
    + {_.map(entityNames, entityName => { + const name = `${menuItemName}-${entityName}`; + const id = utils.getIdFromName(name); + return ( +
  • + + + {entityName} + + +
  • + ); + })} +
+ ); + } + private _onMenuItemClick(name: string): void { + const id = utils.getIdFromName(name); + utils.setUrlHash(id); + 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..90fabf9ff --- /dev/null +++ b/packages/react-shared/src/ts/components/section_header.tsx @@ -0,0 +1,65 @@ +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; +} + +export interface SectionHeaderState { + shouldShowAnchor: boolean; +} + +export class SectionHeader extends React.Component { + public static defaultProps: Partial = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(sectionName); + return ( +
+ + + {sectionName} + + } + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + +
+ ); + } + 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..86fe43507 --- /dev/null +++ b/packages/react-shared/src/ts/components/version_drop_down.tsx @@ -0,0 +1,46 @@ +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[]; +} + +export interface VersionDropDownState {} + +export class VersionDropDown extends React.Component { + public render() { + return ( +
+ + {this._renderDropDownItems()} + +
+ ); + } + private _renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return ; + }); + return items; + } + private _updateSelectedVersion(e: any, index: number, semver: string) { + let path = window.location.pathname; + const lastChar = path[path.length - 1]; + if (_.isFinite(_.parseInt(lastChar))) { + const pathSections = path.split('/'); + pathSections.pop(); + path = pathSections.join('/'); + } + const baseUrl = utils.getCurrentBaseUrl(); + window.location.href = `${baseUrl}${path}/${semver}${window.location.hash}`; + } +} diff --git a/packages/react-shared/src/ts/constants.ts b/packages/react-shared/src/ts/constants.ts deleted file mode 100644 index 20d0c6e92..000000000 --- a/packages/react-shared/src/ts/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const constants = { - DOCS_SCROLL_DURATION_MS: 0, - DOCS_CONTAINER_ID: 'documentation', -}; diff --git a/packages/react-shared/src/ts/globals.d.ts b/packages/react-shared/src/ts/globals.d.ts index e69de29bb..875721533 100644 --- a/packages/react-shared/src/ts/globals.d.ts +++ b/packages/react-shared/src/ts/globals.d.ts @@ -0,0 +1 @@ +declare module 'react-highlight'; diff --git a/packages/react-shared/src/ts/index.ts b/packages/react-shared/src/ts/index.ts index d33638e62..dde77b7b9 100644 --- a/packages/react-shared/src/ts/index.ts +++ b/packages/react-shared/src/ts/index.ts @@ -1,6 +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 } from './types'; +export { HeaderSizes, Styles, MenuSubsectionsBySection } from './types'; -export { utils } from './utils'; -export { constants } from './constants'; +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 index e4fe7bff9..f9d561d1a 100644 --- a/packages/react-shared/src/ts/types.ts +++ b/packages/react-shared/src/ts/types.ts @@ -7,3 +7,7 @@ export enum HeaderSizes { H2 = 'h2', H3 = 'h3', } + +export interface MenuSubsectionsBySection { + [section: string]: string[]; +} diff --git a/packages/react-shared/src/ts/utils.ts b/packages/react-shared/src/ts/utils.ts deleted file mode 100644 index 822b67496..000000000 --- a/packages/react-shared/src/ts/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const utils = { - setUrlHash(anchorId: string) { - window.location.hash = anchorId; - }, -}; 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..79d1d9ca3 --- /dev/null +++ b/packages/react-shared/src/ts/utils/constants.ts @@ -0,0 +1,6 @@ +export const constants = { + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + SCROLL_CONTAINER_ID: 'documentation', + SCROLL_TOP_ID: 'pageScrollTop', +}; 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..7498342b6 --- /dev/null +++ b/packages/react-shared/src/ts/utils/utils.ts @@ -0,0 +1,32 @@ +import * as _ from 'lodash'; +import { scroller } from 'react-scroll'; + +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, + }); + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getCurrentBaseUrl() { + const port = window.location.port; + const hasPort = !_.isUndefined(port); + const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; + return baseUrl; + }, +}; -- cgit v1.2.3