diff options
author | Francesco Agosti <francesco.agosti93@gmail.com> | 2018-12-06 07:44:54 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-06 07:44:54 +0800 |
commit | 21122f0137c39835e5cf15e1a5c2bdbd20030611 (patch) | |
tree | b0b38b40bac88612a5dad019e37087e4a27ff054 | |
parent | e6acc0416a0de54d53c8f50e522ee2a952c58965 (diff) | |
parent | c282c2fcc40b6efbac7c91c3d17b204756359a26 (diff) | |
download | dexon-sol-tools-21122f0137c39835e5cf15e1a5c2bdbd20030611.tar.gz dexon-sol-tools-21122f0137c39835e5cf15e1a5c2bdbd20030611.tar.zst dexon-sol-tools-21122f0137c39835e5cf15e1a5c2bdbd20030611.zip |
Merge pull request #1369 from 0xProject/feature/website/instant-configurator
[website][instant] Instant configurator
21 files changed, 1449 insertions, 176 deletions
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index f06d551c7..7ae27de23 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -1,6 +1,4 @@ -import { ObjectMap } from '@0x/types'; import { BigNumber } from '@0x/utils'; -import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; import * as React from 'react'; import { Provider as ReduxProvider } from 'react-redux'; @@ -11,7 +9,7 @@ import { asyncData } from '../redux/async_data'; import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchOrigin } from '../types'; +import { AccountState, Network, QuoteFetchOrigin, ZeroExInstantBaseConfig } from '../types'; import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -21,24 +19,7 @@ import { Heartbeater } from '../util/heartbeater'; import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory'; import { providerStateFactory } from '../util/provider_state_factory'; -export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps & - Partial<ZeroExInstantProviderOptionalProps>; - -export interface ZeroExInstantProviderRequiredProps { - orderSource: OrderSource; -} - -export interface ZeroExInstantProviderOptionalProps { - provider: Provider; - walletDisplayName: string; - availableAssetDatas: string[]; - defaultAssetBuyAmount: number; - defaultSelectedAssetData: string; - additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; - networkId: Network; - affiliateInfo: AffiliateInfo; - shouldDisableAnalyticsTracking: boolean; -} +export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig; export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> { private readonly _store: Store; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 2d73ba29e..e65961e95 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -177,3 +177,21 @@ export enum ProviderType { Cipher = 'CIPHER', Fallback = 'FALLBACK', } + +export interface ZeroExInstantRequiredBaseConfig { + orderSource: OrderSource; +} + +export interface ZeroExInstantOptionalBaseConfig { + provider: Provider; + walletDisplayName: string; + availableAssetDatas: string[]; + defaultAssetBuyAmount: number; + defaultSelectedAssetData: string; + additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; + networkId: Network; + affiliateInfo: AffiliateInfo; + shouldDisableAnalyticsTracking: boolean; +} + +export type ZeroExInstantBaseConfig = ZeroExInstantRequiredBaseConfig & Partial<ZeroExInstantOptionalBaseConfig>; diff --git a/packages/website/package.json b/packages/website/package.json index dc10c7b1c..5d2e563e9 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -20,6 +20,8 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { + "@0x/asset-buyer": "^3.0.2", + "@0x/contract-addresses": "^2.0.0", "@0x/contract-wrappers": "^4.1.1", "@0x/json-schemas": "^2.1.2", "@0x/order-utils": "^3.0.4", @@ -46,6 +48,7 @@ "numeral": "^2.0.6", "polished": "^1.9.2", "query-string": "^6.0.0", + "rc-slider": "^8.6.3", "react": "^16.4.2", "react-copy-to-clipboard": "^5.0.0", "react-document-title": "^2.0.3", @@ -54,6 +57,7 @@ "react-popper": "^1.0.0-beta.6", "react-redux": "^5.0.3", "react-scroll": "0xproject/react-scroll#pr-330-and-replace-state", + "react-syntax-highlighter": "^10.1.1", "react-tooltip": "^3.2.7", "react-typist": "^2.0.4", "redux": "^3.6.0", @@ -77,12 +81,14 @@ "@types/node": "*", "@types/numeral": "^0.0.22", "@types/query-string": "^5.1.0", + "@types/rc-slider": "^8.6.0", "@types/react": "^16.4.2", "@types/react-copy-to-clipboard": "^4.2.0", "@types/react-dom": "^16.0.7", "@types/react-helmet": "^5.0.6", "@types/react-redux": "^4.4.37", "@types/react-scroll": "1.5.3", + "@types/react-syntax-highlighter": "^0.0.8", "@types/react-tap-event-plugin": "0.0.30", "@types/redux": "^3.6.0", "@types/web3-provider-engine": "^14.0.0", diff --git a/packages/website/public/index.html b/packages/website/public/index.html index a8a61f8ad..538eca6d9 100644 --- a/packages/website/public/index.html +++ b/packages/website/public/index.html @@ -1,95 +1,132 @@ <!DOCTYPE html> <html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" /> + <meta property="og:type" content="website" /> + <meta property="og:title" content="0x" /> + <meta + property="og:description" + content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" + /> + <meta property="og:image" content="/images/og_image.png" /> + <title>0x: The Protocol for Trading Tokens</title> + <link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" /> + <link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" /> + <link rel="stylesheet" href="/css/material-design-iconic-font.min.css" /> + <link rel="stylesheet" href="/css/roboto.css" /> + <link rel="stylesheet" href="/css/roboto_mono.css" /> + <link rel="stylesheet" href="/css/basscss_responsive_custom.css" /> + <link rel="stylesheet" href="/css/basscss_responsive_padding.css" /> + <link rel="stylesheet" href="/css/basscss_responsive_margin.css" /> + <link rel="stylesheet" href="/css/basscss_responsive_type_scale.css" /> + </head> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta name="description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" /> - <meta property="og:type" content="website" /> - <meta property="og:title" content="0x" /> - <meta property="og:description" content="An Open Protocol For Decentralized Exchange On The Ethereum Blockchain" /> - <meta property="og:image" content="/images/og_image.png" /> - <title>0x: The Protocol for Trading Tokens</title> - <link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" /> - <link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" /> - <link rel="stylesheet" href="/css/github-gist.css"> - <link rel="stylesheet" href="/css/material-design-iconic-font.min.css"> - <link rel="stylesheet" href="/css/roboto.css"> - <link rel="stylesheet" href="/css/roboto_mono.css"> - <link rel="stylesheet" href="/css/basscss_responsive_custom.css"> - <link rel="stylesheet" href="/css/basscss_responsive_padding.css"> - <link rel="stylesheet" href="/css/basscss_responsive_margin.css"> - <link rel="stylesheet" href="/css/basscss_responsive_type_scale.css"> -</head> + <body style="margin: 0px; min-width: 355px;"> + <!-- Heap SDK --> + <script type="text/javascript"> + (window.heap = window.heap || []), + (heap.load = function(e, t) { + (window.heap.appid = e), (window.heap.config = t = t || {}); + var r = t.forceSSL || 'https:' === document.location.protocol, + a = document.createElement('script'); + (a.type = 'text/javascript'), + (a.async = !0), + (a.src = (r ? 'https:' : 'http:') + '//cdn.heapanalytics.com/js/heap-' + e + '.js'); + var n = document.getElementsByTagName('script')[0]; + n.parentNode.insertBefore(a, n); + for ( + var o = function(e) { + return function() { + heap.push([e].concat(Array.prototype.slice.call(arguments, 0))); + }; + }, + p = [ + 'addEventProperties', + 'addUserProperties', + 'clearEventProperties', + 'identify', + 'resetIdentity', + 'removeEventProperty', + 'setEventProperties', + 'track', + 'unsetEventProperty', + ], + c = 0; + c < p.length; + c++ + ) + heap[p[c]] = o(p[c]); + }); + heap.load('410099666'); + </script> + <!-- End Heap SDK --> + <!-- Global site tag (gtag.js) - Google Analytics --> + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script> + <script> + window.dataLayer = window.dataLayer || []; + function gtag() { + dataLayer.push(arguments); + } + gtag('js', new Date()); -<body style="margin: 0px; min-width: 355px;"> - <!-- Heap SDK --> - <script type="text/javascript"> - window.heap = window.heap || [], heap.load = function (e, t) { window.heap.appid = e, window.heap.config = t = t || {}; var r = t.forceSSL || "https:" === document.location.protocol, a = document.createElement("script"); a.type = "text/javascript", a.async = !0, a.src = (r ? "https:" : "http:") + "//cdn.heapanalytics.com/js/heap-" + e + ".js"; var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(a, n); for (var o = function (e) { return function () { heap.push([e].concat(Array.prototype.slice.call(arguments, 0))) } }, p = ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"], c = 0; c < p.length; c++)heap[p[c]] = o(p[c]) }; - heap.load("410099666"); - </script> - <!-- End Heap SDK --> - <!-- Global site tag (gtag.js) - Google Analytics --> - <script async src="https://www.googletagmanager.com/gtag/js?id=UA-98720122-1"></script> - <script> - window.dataLayer = window.dataLayer || []; - function gtag() { - dataLayer.push(arguments); - } - gtag('js', new Date()); + gtag('config', 'UA-98720122-1'); + </script> + <!-- End Google Analytics --> + <!-- Facebook SDK --> + <div id="fb-root"></div> + <script> + (function(d, s, id) { + var js, + fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); + js.id = id; + js.src = '//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1687545238205192'; + fjs.parentNode.insertBefore(js, fjs); + })(document, 'script', 'facebook-jssdk'); + </script> + <div id="app"></div> + <!-- End Facebook SDK --> + <!-- Twitter SDK --> + <script> + window.twttr = (function(d, s, id) { + var js, + fjs = d.getElementsByTagName(s)[0], + t = window.twttr || {}; + if (d.getElementById(id)) return t; + js = d.createElement(s); + js.id = id; + js.src = 'https://platform.twitter.com/widgets.js'; + fjs.parentNode.insertBefore(js, fjs); - gtag('config', 'UA-98720122-1'); - </script> - <!-- End Google Analytics --> - <!-- Facebook SDK --> - <div id="fb-root"></div> - <script> - (function (d, s, id) { - var js, - fjs = d.getElementsByTagName(s)[0]; - if (d.getElementById(id)) return; - js = d.createElement(s); - js.id = id; - js.src = '//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1687545238205192'; - fjs.parentNode.insertBefore(js, fjs); - })(document, 'script', 'facebook-jssdk'); - </script> - <div id="app"></div> - <!-- End Facebook SDK --> - <!-- Twitter SDK --> - <script> - window.twttr = (function (d, s, id) { - var js, - fjs = d.getElementsByTagName(s)[0], - t = window.twttr || {}; - if (d.getElementById(id)) return t; - js = d.createElement(s); - js.id = id; - js.src = 'https://platform.twitter.com/widgets.js'; - fjs.parentNode.insertBefore(js, fjs); - - t._e = []; - t.ready = function (f) { - t._e.push(f); - }; - return t; - })(document, 'script', 'twitter-wjs'); - </script> - <!-- End Twitter SDK --> - <!-- Hotjar Tracking Code for https://0xproject.com/ --> - <script> - (function (h, o, t, j, a, r) { - h.hj = h.hj || function () { (h.hj.q = h.hj.q || []).push(arguments) }; - h._hjSettings = { hjid: 935597, hjsv: 6 }; - a = o.getElementsByTagName('head')[0]; - r = o.createElement('script'); r.async = 1; - r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; - a.appendChild(r); - })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); - </script> - <!-- End Hotjar Tracking Code --> - <!-- Main --> - <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script> -</body> - -</html>
\ No newline at end of file + t._e = []; + t.ready = function(f) { + t._e.push(f); + }; + return t; + })(document, 'script', 'twitter-wjs'); + </script> + <!-- End Twitter SDK --> + <!-- Hotjar Tracking Code for https://0xproject.com/ --> + <script> + (function(h, o, t, j, a, r) { + h.hj = + h.hj || + function() { + (h.hj.q = h.hj.q || []).push(arguments); + }; + h._hjSettings = { hjid: 935597, hjsv: 6 }; + a = o.getElementsByTagName('head')[0]; + r = o.createElement('script'); + r.async = 1; + r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; + a.appendChild(r); + })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); + </script> + <!-- End Hotjar Tracking Code --> + <!-- Main --> + <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script> + </body> +</html> diff --git a/packages/website/ts/components/ui/check_mark.tsx b/packages/website/ts/components/ui/check_mark.tsx new file mode 100644 index 000000000..86e427c8b --- /dev/null +++ b/packages/website/ts/components/ui/check_mark.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; + +import { colors } from '@0x/react-shared'; + +export interface CheckMarkProps { + color?: string; + isChecked?: boolean; +} + +export const CheckMark: React.StatelessComponent<CheckMarkProps> = ({ color, isChecked }) => ( + <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> + <circle + cx="8.5" + cy="8.5" + r="8.5" + fill={isChecked ? color : 'white'} + stroke={isChecked ? undefined : '#CCCCCC'} + /> + <path + d="M2.5 4.5L1.79289 5.20711L2.5 5.91421L3.20711 5.20711L2.5 4.5ZM-0.707107 2.70711L1.79289 5.20711L3.20711 3.79289L0.707107 1.29289L-0.707107 2.70711ZM3.20711 5.20711L7.70711 0.707107L6.29289 -0.707107L1.79289 3.79289L3.20711 5.20711Z" + transform="translate(5 6.5)" + fill="white" + /> + </svg> +); + +CheckMark.displayName = 'Check'; + +CheckMark.defaultProps = { + color: colors.mediumBlue, +}; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index 7eab2a50f..ae00851e5 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -1,11 +1,15 @@ import { TextAlignProperty } from 'csstype'; +import { darken } from 'polished'; import * as React from 'react'; +import { styled } from 'ts/style/theme'; + type StringOrNum = string | number; export type ContainerTag = 'div' | 'span'; export interface ContainerProps { + margin?: string; marginTop?: StringOrNum; marginBottom?: StringOrNum; marginRight?: StringOrNum; @@ -17,10 +21,13 @@ export interface ContainerProps { paddingLeft?: StringOrNum; backgroundColor?: string; background?: string; + border?: string; + borderTop?: string; borderRadius?: StringOrNum; borderBottomLeftRadius?: StringOrNum; borderBottomRightRadius?: StringOrNum; borderBottom?: StringOrNum; + borderColor?: string; maxWidth?: StringOrNum; maxHeight?: StringOrNum; width?: StringOrNum; @@ -37,15 +44,32 @@ export interface ContainerProps { right?: string; bottom?: string; zIndex?: number; + float?: 'right' | 'left'; Tag?: ContainerTag; cursor?: string; id?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible'; + overflowY?: 'scroll' | 'hidden' | 'auto' | 'visible'; + shouldDarkenOnHover?: boolean; + hasBoxShadow?: boolean; + shouldAddBoxShadowOnHover?: boolean; } -export const Container: React.StatelessComponent<ContainerProps> = props => { - const { children, className, Tag, isHidden, id, onClick, ...style } = props; +export const PlainContainer: React.StatelessComponent<ContainerProps> = props => { + const { + children, + className, + Tag, + isHidden, + id, + onClick, + shouldDarkenOnHover, + shouldAddBoxShadowOnHover, + hasBoxShadow, + // tslint:disable-next-line:trailing-comma + ...style + } = props; const visibility = isHidden ? 'hidden' : undefined; return ( <Tag id={id} style={{ ...style, visibility }} className={className} onClick={onClick}> @@ -54,6 +78,20 @@ export const Container: React.StatelessComponent<ContainerProps> = props => { ); }; +const BOX_SHADOW = '0px 3px 10px rgba(0, 0, 0, 0.3)'; + +export const Container = styled(PlainContainer)` + box-sizing: border-box; + ${props => (props.hasBoxShadow ? `box-shadow: ${BOX_SHADOW}` : '')}; + &:hover { + ${props => + props.shouldDarkenOnHover + ? `background-color: ${props.backgroundColor ? darken(0.05, props.backgroundColor) : 'none'} !important` + : ''}; + ${props => (props.shouldAddBoxShadowOnHover ? `box-shadow: ${BOX_SHADOW}` : '')}; + } +`; + Container.defaultProps = { Tag: 'div', }; diff --git a/packages/website/ts/components/ui/input.tsx b/packages/website/ts/components/ui/input.tsx index e5f4f6c70..d21b9fd0e 100644 --- a/packages/website/ts/components/ui/input.tsx +++ b/packages/website/ts/components/ui/input.tsx @@ -8,6 +8,8 @@ export interface InputProps { width?: string; fontSize?: string; fontColor?: string; + border?: string; + padding?: string; placeholderColor?: string; placeholder?: string; backgroundColor?: string; @@ -21,11 +23,13 @@ const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, pl export const Input = styled(PlainInput)` font-size: ${props => props.fontSize}; width: ${props => props.width}; - padding: 0.8em 1.2em; + padding: ${props => props.padding}; border-radius: 3px; + box-sizing: border-box; font-family: 'Roboto Mono'; color: ${props => props.fontColor}; - border: none; + border: ${props => props.border}; + outline: none; background-color: ${props => props.backgroundColor}; &::placeholder { color: ${props => props.placeholderColor}; @@ -38,6 +42,8 @@ Input.defaultProps = { fontColor: colors.darkestGrey, placeholderColor: colors.darkGrey, fontSize: '12px', + border: 'none', + padding: '0.8em 1.2em', }; Input.displayName = 'Input'; diff --git a/packages/website/ts/components/ui/multi_select.tsx b/packages/website/ts/components/ui/multi_select.tsx new file mode 100644 index 000000000..2cf44cae1 --- /dev/null +++ b/packages/website/ts/components/ui/multi_select.tsx @@ -0,0 +1,66 @@ +import { colors } from '@0x/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Container } from './container'; + +export interface MultiSelectItemConfig { + value: string; + renderItemContent: (isSelected: boolean) => React.ReactNode; + onClick?: () => void; +} + +export interface MultiSelectProps { + selectedValues?: string[]; + items: MultiSelectItemConfig[]; + backgroundColor?: string; + height?: string; +} + +export class MultiSelect extends React.Component<MultiSelectProps> { + public static defaultProps = { + backgroundColor: colors.white, + }; + public render(): React.ReactNode { + const { items, backgroundColor, selectedValues, height } = this.props; + return ( + <Container + backgroundColor={backgroundColor} + borderRadius="4px" + width="100%" + height={height} + overflowY="scroll" + > + {_.map(items, item => ( + <MultiSelectItem + key={item.value} + renderItemContent={item.renderItemContent} + backgroundColor={backgroundColor} + onClick={item.onClick} + isSelected={_.isUndefined(selectedValues) || _.includes(selectedValues, item.value)} + /> + ))} + </Container> + ); + } +} + +export interface MultiSelectItemProps { + renderItemContent: (isSelected: boolean) => React.ReactNode; + isSelected?: boolean; + onClick?: () => void; + backgroundColor?: string; +} + +export const MultiSelectItem: React.StatelessComponent<MultiSelectItemProps> = ({ + renderItemContent, + isSelected, + onClick, + backgroundColor, +}) => ( + <Container cursor="pointer" shouldDarkenOnHover={true} onClick={onClick} backgroundColor={backgroundColor}> + <Container borderBottom={`1px solid ${colors.lightestGrey}`} margin="0px 15px" padding="10px 0px"> + {renderItemContent(isSelected)} + </Container> + </Container> +); diff --git a/packages/website/ts/components/ui/select.tsx b/packages/website/ts/components/ui/select.tsx new file mode 100644 index 000000000..e4fb50f59 --- /dev/null +++ b/packages/website/ts/components/ui/select.tsx @@ -0,0 +1,170 @@ +import { colors } from '@0x/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { zIndex } from 'ts/style/z_index'; + +import { Container } from './container'; +import { Overlay } from './overlay'; +import { Text } from './text'; + +export interface SelectItemConfig { + text: string; + onClick?: () => void; +} + +export interface SelectProps { + value: string; + label?: string; + items: SelectItemConfig[]; + onOpen?: () => void; + border?: string; + fontSize?: string; + iconSize?: number; + textColor?: string; + labelColor?: string; + backgroundColor?: string; +} + +export interface SelectState { + isOpen: boolean; +} + +export class Select extends React.Component<SelectProps, SelectState> { + public static defaultProps = { + items: [] as SelectItemConfig[], + textColor: colors.black, + backgroundColor: colors.white, + fontSize: '16px', + iconSize: 25, + }; + public state: SelectState = { + isOpen: false, + }; + public render(): React.ReactNode { + const { value, label, items, border, textColor, labelColor, backgroundColor, fontSize, iconSize } = this.props; + const { isOpen } = this.state; + const hasItems = !_.isEmpty(items); + const borderRadius = isOpen ? '4px 4px 0px 0px' : '4px'; + return ( + <React.Fragment> + {isOpen && ( + <Overlay + style={{ + zIndex: zIndex.overlay, + backgroundColor: 'rgba(255, 255, 255, 0)', + }} + onClick={this._closeDropdown} + /> + )} + <Container position="relative"> + <Container + cursor={hasItems ? 'pointer' : undefined} + onClick={this._handleDropdownClick} + borderRadius={borderRadius} + hasBoxShadow={isOpen} + border={border} + backgroundColor={backgroundColor} + padding="0.5em 0.8em" + width="100%" + > + <Container className="flex justify-between"> + <Text fontSize={fontSize} fontColor={textColor}> + {value} + </Text> + <Container> + {label && ( + <Text fontSize={fontSize} fontColor={labelColor}> + {label} + </Text> + )} + {hasItems && ( + <Container marginLeft="5px" display="inline-block"> + <i + className="zmdi zmdi-chevron-down" + style={{ fontSize: iconSize, color: colors.darkGrey }} + /> + </Container> + )} + </Container> + </Container> + </Container> + {isOpen && ( + <Container + width="100%" + position="absolute" + onClick={this._closeDropdown} + zIndex={zIndex.aboveOverlay} + hasBoxShadow={true} + > + {_.map(items, (item, index) => ( + <SelectItem + key={item.text} + {...item} + isLast={index === items.length - 1} + backgroundColor={backgroundColor} + textColor={textColor} + border={border} + /> + ))} + </Container> + )} + </Container> + </React.Fragment> + ); + } + private readonly _handleDropdownClick = (): void => { + if (_.isEmpty(this.props.items)) { + return; + } + const isOpen = !this.state.isOpen; + this.setState({ + isOpen, + }); + + if (isOpen && this.props.onOpen) { + this.props.onOpen(); + } + }; + private readonly _closeDropdown = (): void => { + this.setState({ + isOpen: false, + }); + }; +} + +export interface SelectItemProps extends SelectItemConfig { + text: string; + onClick?: () => void; + isLast: boolean; + backgroundColor?: string; + border?: string; + textColor?: string; + fontSize?: string; +} + +export const SelectItem: React.StatelessComponent<SelectItemProps> = ({ + text, + onClick, + isLast, + border, + backgroundColor, + textColor, + fontSize, +}) => ( + <Container + onClick={onClick} + cursor="pointer" + backgroundColor={backgroundColor} + padding="0.8em" + borderTop="0" + border={border} + shouldDarkenOnHover={true} + borderRadius={isLast ? '0px 0px 4px 4px' : undefined} + width="100%" + > + <Text fontSize={fontSize} fontColor={textColor}> + {text} + </Text> + </Container> +); diff --git a/packages/website/ts/pages/documentation/developers_page.tsx b/packages/website/ts/pages/documentation/developers_page.tsx index a84be7bfe..fcca2b6ad 100644 --- a/packages/website/ts/pages/documentation/developers_page.tsx +++ b/packages/website/ts/pages/documentation/developers_page.tsx @@ -2,6 +2,7 @@ import { colors, constants as sharedConstants, utils as sharedUtils } from '@0x/ import * as _ from 'lodash'; import * as React from 'react'; import DocumentTitle from 'react-document-title'; +import { Helmet } from 'react-helmet'; import { DocsLogo } from 'ts/components/documentation/docs_logo'; import { DocsTopBar } from 'ts/components/documentation/docs_top_bar'; import { Container } from 'ts/components/ui/container'; @@ -146,6 +147,9 @@ export class DevelopersPage extends React.Component<DevelopersPageProps, Develop } 50%, ${colors.white} 100%)`} > <DocumentTitle title="0x Docs" /> + <Helmet> + <link rel="stylesheet" href="/css/github-gist.css" /> + </Helmet> <Container className="flex mx-auto" height="100vh"> <Container className="sm-hide xs-hide relative" diff --git a/packages/website/ts/pages/instant/action_link.tsx b/packages/website/ts/pages/instant/action_link.tsx new file mode 100644 index 000000000..c196f03ef --- /dev/null +++ b/packages/website/ts/pages/instant/action_link.tsx @@ -0,0 +1,46 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; +import { colors } from 'ts/style/colors'; +import { utils } from 'ts/utils/utils'; + +export interface ActionLinkProps { + displayText: string; + linkSrc?: string; + onClick?: () => void; + fontSize?: number; + color?: string; + className?: string; +} + +export class ActionLink extends React.Component<ActionLinkProps> { + public static defaultProps = { + fontSize: 16, + color: colors.white, + }; + public render(): React.ReactNode { + const { displayText, fontSize, color, className } = this.props; + return ( + <Container className={`flex items-center ${className}`} onClick={this._handleClick} cursor="pointer"> + <Container> + <Text fontSize="16px" fontColor={color}> + {displayText} + </Text> + </Container> + <Container paddingTop="1px" paddingLeft="6px"> + <i className="zmdi zmdi-chevron-right bold" style={{ fontSize, color }} /> + </Container> + </Container> + ); + } + + private readonly _handleClick = (event: React.MouseEvent<HTMLElement>) => { + if (!_.isUndefined(this.props.onClick)) { + this.props.onClick(); + } else if (!_.isUndefined(this.props.linkSrc)) { + utils.openUrl(this.props.linkSrc); + } + }; +} diff --git a/packages/website/ts/pages/instant/code_demo.tsx b/packages/website/ts/pages/instant/code_demo.tsx new file mode 100644 index 000000000..1bc1980e7 --- /dev/null +++ b/packages/website/ts/pages/instant/code_demo.tsx @@ -0,0 +1,177 @@ +import * as React from 'react'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import SyntaxHighlighter from 'react-syntax-highlighter'; + +import { Button } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; +import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; +import { zIndex } from 'ts/style/z_index'; + +const CustomPre = styled.pre` + margin: 0px; + line-height: 24px; + overflow: scroll; + width: 600px; + height: 100%; + max-height: 800px; + border-radius: 4px; + code { + background-color: inherit !important; + border-radius: 0px; + font-family: 'Roboto Mono', sans-serif; + border: none; + } + code:first-of-type { + background-color: #2a2a2a !important; + color: #999; + min-height: 98%; + text-align: center; + padding-right: 5px !important; + padding-left: 5px; + margin-right: 15px; + line-height: 25px; + padding-top: 10px; + } + code:last-of-type { + position: relative; + top: 10px; + } +`; + +const customStyle = { + 'hljs-comment': { + color: '#7e7887', + }, + 'hljs-quote': { + color: '#7e7887', + }, + 'hljs-variable': { + color: '#be4678', + }, + 'hljs-template-variable': { + color: '#be4678', + }, + 'hljs-attribute': { + color: '#be4678', + }, + 'hljs-regexp': { + color: '#be4678', + }, + 'hljs-link': { + color: '#be4678', + }, + 'hljs-tag': { + color: '#61f5ff', + }, + 'hljs-name': { + color: '#61f5ff', + }, + 'hljs-selector-id': { + color: '#be4678', + }, + 'hljs-selector-class': { + color: '#be4678', + }, + 'hljs-number': { + color: '#c994ff', + }, + 'hljs-meta': { + color: '#aa573c', + }, + 'hljs-built_in': { + color: '#aa573c', + }, + 'hljs-builtin-name': { + color: '#aa573c', + }, + 'hljs-literal': { + color: '#aa573c', + }, + 'hljs-type': { + color: '#aa573c', + }, + 'hljs-params': { + color: '#aa573c', + }, + 'hljs-string': { + color: '#bcff88', + }, + 'hljs-symbol': { + color: '#2a9292', + }, + 'hljs-bullet': { + color: '#2a9292', + }, + 'hljs-title': { + color: '#576ddb', + }, + 'hljs-section': { + color: '#576ddb', + }, + 'hljs-keyword': { + color: '#955ae7', + }, + 'hljs-selector-tag': { + color: '#955ae7', + }, + 'hljs-deletion': { + color: '#19171c', + display: 'inline-block', + width: '100%', + backgroundColor: '#be4678', + }, + 'hljs-addition': { + color: '#19171c', + display: 'inline-block', + width: '100%', + backgroundColor: '#2a9292', + }, + hljs: { + display: 'block', + overflowX: 'hidden', + background: colors.instantSecondaryBackground, + color: 'white', + fontSize: '12px', + }, + 'hljs-emphasis': { + fontStyle: 'italic', + }, + 'hljs-strong': { + fontWeight: 'bold', + }, +}; + +export interface CodeDemoProps { + children: string; +} + +export interface CodeDemoState { + didCopyCode: boolean; +} + +export class CodeDemo extends React.Component<CodeDemoProps, CodeDemoState> { + public state: CodeDemoState = { + didCopyCode: false, + }; + public render(): React.ReactNode { + const copyButtonText = this.state.didCopyCode ? 'Copied!' : 'Copy'; + return ( + <Container position="relative" height="100%"> + <Container position="absolute" top="10px" right="10px" zIndex={zIndex.overlay - 1}> + <CopyToClipboard text={this.props.children} onCopy={this._handleCopyClick}> + <Button fontSize="14px"> + <b>{copyButtonText}</b> + </Button> + </CopyToClipboard> + </Container> + <SyntaxHighlighter language="html" style={customStyle} showLineNumbers={true} PreTag={CustomPre}> + {this.props.children} + </SyntaxHighlighter> + </Container> + ); + } + private readonly _handleCopyClick = () => { + this.setState({ didCopyCode: true }); + }; +} diff --git a/packages/website/ts/pages/instant/config_generator.tsx b/packages/website/ts/pages/instant/config_generator.tsx new file mode 100644 index 000000000..fe70ef04c --- /dev/null +++ b/packages/website/ts/pages/instant/config_generator.tsx @@ -0,0 +1,306 @@ +import { StandardRelayerAPIOrderProvider } from '@0x/asset-buyer'; +import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; +import { assetDataUtils } from '@0x/order-utils'; +import { ObjectMap } from '@0x/types'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { CheckMark } from 'ts/components/ui/check_mark'; +import { Container } from 'ts/components/ui/container'; +import { MultiSelect } from 'ts/components/ui/multi_select'; +import { Select, SelectItemConfig } from 'ts/components/ui/select'; +import { Spinner } from 'ts/components/ui/spinner'; +import { Text } from 'ts/components/ui/text'; +import { ConfigGeneratorAddressInput } from 'ts/pages/instant/config_generator_address_input'; +import { FeePercentageSlider } from 'ts/pages/instant/fee_percentage_slider'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; +import { constants } from 'ts/utils/constants'; + +import { assetMetaDataMap } from '../../../../instant/src/data/asset_meta_data_map'; +import { ERC20AssetMetaData, ZeroExInstantBaseConfig } from '../../../../instant/src/types'; + +export interface ConfigGeneratorProps { + value: ZeroExInstantBaseConfig; + onConfigChange: (config: ZeroExInstantBaseConfig) => void; +} + +export interface ConfigGeneratorState { + isLoadingAvailableTokens: boolean; + // Address to token info + availableTokens?: ObjectMap<ERC20AssetMetaData>; +} + +const SRA_ENDPOINTS = ['https://api.radarrelay.com/0x/v2/', 'https://sra.bamboorelay.com/0x/v2/']; + +export class ConfigGenerator extends React.Component<ConfigGeneratorProps, ConfigGeneratorState> { + public state: ConfigGeneratorState = { + isLoadingAvailableTokens: true, + }; + public componentDidMount(): void { + // tslint:disable-next-line:no-floating-promises + this._setAvailableAssetsFromOrderProvider(); + } + public componentDidUpdate(prevProps: ConfigGeneratorProps): void { + if (prevProps.value.orderSource !== this.props.value.orderSource) { + // tslint:disable-next-line:no-floating-promises + this._setAvailableAssetsFromOrderProvider(); + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: undefined, + }; + this.props.onConfigChange(newConfig); + } + } + public render(): React.ReactNode { + const { value } = this.props; + if (!_.isString(value.orderSource)) { + throw new Error('ConfigGenerator component only supports string values as an orderSource.'); + } + return ( + <Container minWidth="350px"> + <ConfigGeneratorSection title="Standard relayer API endpoint"> + <Select value={value.orderSource} items={this._generateItems()} /> + </ConfigGeneratorSection> + <ConfigGeneratorSection {...this._getTokenSelectorProps()}> + {this._renderTokenMultiSelectOrSpinner()} + </ConfigGeneratorSection> + <ConfigGeneratorSection title="Transaction fee ETH address" marginBottom="10px" isOptional={true}> + <ConfigGeneratorAddressInput + value={value.affiliateInfo ? value.affiliateInfo.feeRecipient : ''} + onChange={this._handleAffiliateAddressChange} + /> + </ConfigGeneratorSection> + <ConfigGeneratorSection + title="Fee percentage" + actionText="Learn more" + onActionTextClick={this._handleAffiliatePercentageLearnMoreClick} + > + <FeePercentageSlider + value={value.affiliateInfo.feePercentage} + onChange={this._handleAffiliatePercentageChange} + /> + </ConfigGeneratorSection> + </Container> + ); + } + private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => { + if (_.isEmpty(this.state.availableTokens)) { + return { + title: 'What tokens can users buy?', + }; + } + if (_.isUndefined(this.props.value.availableAssetDatas)) { + return { + title: 'What tokens can users buy?', + actionText: 'Unselect All', + onActionTextClick: this._handleUnselectAllClick, + }; + } + return { + title: 'What tokens can users buy?', + actionText: 'Select All', + onActionTextClick: this._handleSelectAllClick, + }; + }; + private readonly _generateItems = (): SelectItemConfig[] => { + return _.map(SRA_ENDPOINTS, endpoint => ({ + text: endpoint, + onClick: this._handleSRASelection.bind(this, endpoint), + })); + }; + private readonly _handleAffiliatePercentageLearnMoreClick = (): void => { + window.open(`${WebsitePaths.Wiki}#Learn-About-Affiliate-Fees`, '_blank'); + }; + private readonly _handleSRASelection = (sraEndpoint: string) => { + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + orderSource: sraEndpoint, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleAffiliateAddressChange = (address: string, isValid: boolean) => { + const oldConfig: ZeroExInstantBaseConfig = this.props.value; + const newConfig: ZeroExInstantBaseConfig = { + ...oldConfig, + affiliateInfo: { + feeRecipient: address, + feePercentage: oldConfig.affiliateInfo.feePercentage, + }, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleAffiliatePercentageChange = (value: number) => { + const oldConfig: ZeroExInstantBaseConfig = this.props.value; + const newConfig: ZeroExInstantBaseConfig = { + ...oldConfig, + affiliateInfo: { + feeRecipient: oldConfig.affiliateInfo.feeRecipient, + feePercentage: value, + }, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleSelectAllClick = () => { + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: undefined, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleUnselectAllClick = () => { + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: [], + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleTokenClick = (assetData: string) => { + const { value } = this.props; + let newAvailableAssetDatas: string[] = []; + const allKnownAssetDatas = _.keys(this.state.availableTokens); + const availableAssetDatas = value.availableAssetDatas; + if (_.isUndefined(availableAssetDatas)) { + // It being undefined means it's all tokens. + newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData); + } else if (!_.includes(availableAssetDatas, assetData)) { + // Add it + newAvailableAssetDatas = [...availableAssetDatas, assetData]; + if (newAvailableAssetDatas.length === allKnownAssetDatas.length) { + // If all tokens are manually selected, just show none. + newAvailableAssetDatas = undefined; + } + } else { + // Remove it + newAvailableAssetDatas = _.pull(availableAssetDatas, assetData); + } + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: newAvailableAssetDatas, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _setAvailableAssetsFromOrderProvider = async (): Promise<void> => { + const { value } = this.props; + if (!_.isUndefined(value.orderSource) && _.isString(value.orderSource)) { + this.setState({ isLoadingAvailableTokens: true }); + const networkId = constants.NETWORK_ID_MAINNET; + const sraOrderProvider = new StandardRelayerAPIOrderProvider(value.orderSource, networkId); + const etherTokenAddress = getContractAddressesForNetworkOrThrow(networkId).etherToken; + const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress); + const assetDatas = await sraOrderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData); + const availableTokens = _.reduce( + assetDatas, + (acc, assetData) => { + const metaDataIfExists = assetMetaDataMap[assetData] as ERC20AssetMetaData; + if (metaDataIfExists) { + acc[assetData] = metaDataIfExists; + } + return acc; + }, + {} as ObjectMap<ERC20AssetMetaData>, + ); + this.setState({ availableTokens, isLoadingAvailableTokens: false }); + } + }; + private readonly _renderTokenMultiSelectOrSpinner = (): React.ReactNode => { + const { value } = this.props; + const { availableTokens, isLoadingAvailableTokens } = this.state; + const multiSelectHeight = '200px'; + if (isLoadingAvailableTokens) { + return ( + <Container + className="flex flex-column items-center justify-center" + height={multiSelectHeight} + backgroundColor={colors.white} + borderRadius="4px" + width="100%" + > + <Container position="relative" left="12px" marginBottom="20px"> + <Spinner /> + </Container> + <Text fontSize="16px">Loading...</Text> + </Container> + ); + } + const availableAssetDatas = _.keys(availableTokens); + if (availableAssetDatas.length === 0) { + return ( + <Container + className="flex flex-column items-center justify-center" + height={multiSelectHeight} + backgroundColor={colors.white} + borderRadius="4px" + width="100%" + > + <Text fontSize="16px">No tokens available. Try another endpoint?</Text> + </Container> + ); + } + const items = _.map(_.keys(availableTokens), assetData => { + const metaData = availableTokens[assetData]; + return { + value: assetData, + renderItemContent: (isSelected: boolean) => ( + <Container className="flex items-center"> + <Container marginRight="10px"> + <CheckMark isChecked={isSelected} /> + </Container> + <Text + fontSize="16px" + fontColor={isSelected ? colors.mediumBlue : colors.darkerGrey} + fontWeight={300} + > + <b>{metaData.symbol.toUpperCase()}</b> — {metaData.name} + </Text> + </Container> + ), + onClick: this._handleTokenClick.bind(this, assetData), + }; + }); + return <MultiSelect items={items} selectedValues={value.availableAssetDatas} height={multiSelectHeight} />; + }; +} + +export interface ConfigGeneratorSectionProps { + title: string; + actionText?: string; + onActionTextClick?: () => void; + isOptional?: boolean; + marginBottom?: string; +} + +export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = ({ + title, + actionText, + onActionTextClick, + isOptional, + marginBottom, + children, +}) => ( + <Container marginBottom={marginBottom}> + <Container marginBottom="10px" className="flex justify-between items-center"> + <Container> + <Text fontColor={colors.white} fontSize="16px" lineHeight="18px" display="inline"> + {title} + </Text> + {isOptional && ( + <Text fontColor={colors.grey} fontSize="16px" lineHeight="18px" display="inline"> + {' '} + (optional) + </Text> + )} + </Container> + {actionText && ( + <Text fontSize="12px" fontColor={colors.grey} onClick={onActionTextClick}> + {actionText} + </Text> + )} + </Container> + {children} + </Container> +); + +ConfigGeneratorSection.defaultProps = { + marginBottom: '30px', +}; diff --git a/packages/website/ts/pages/instant/config_generator_address_input.tsx b/packages/website/ts/pages/instant/config_generator_address_input.tsx new file mode 100644 index 000000000..ccbaf4482 --- /dev/null +++ b/packages/website/ts/pages/instant/config_generator_address_input.tsx @@ -0,0 +1,59 @@ +import { colors } from '@0x/react-shared'; +import { addressUtils } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Container } from 'ts/components/ui/container'; +import { Input } from 'ts/components/ui/input'; +import { Text } from 'ts/components/ui/text'; + +export interface ConfigGeneratorAddressInputProps { + value?: string; + onChange?: (address: string, isValid: boolean) => void; +} + +export interface ConfigGeneratorAddressInputState { + errMsg: string; +} + +export class ConfigGeneratorAddressInput extends React.Component< + ConfigGeneratorAddressInputProps, + ConfigGeneratorAddressInputState +> { + public state = { + errMsg: '', + }; + public render(): React.ReactNode { + const { errMsg } = this.state; + const hasError = !_.isEmpty(errMsg); + const border = hasError ? '1px solid red' : undefined; + return ( + <Container height="80px"> + <Input + width="100%" + fontSize="16px" + padding="0.7em 1em" + value={this.props.value} + onChange={this._handleChange} + placeholder="0xe99...aa8da4" + border={border} + /> + <Container marginTop="5px" isHidden={!hasError} height="25px"> + <Text fontSize="14px" fontColor={colors.grey} fontStyle="italic"> + {errMsg} + </Text> + </Container> + </Container> + ); + } + + private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const address = event.target.value; + const isValidAddress = addressUtils.isAddress(address.toLowerCase()) || address === ''; + const errMsg = isValidAddress ? '' : 'Please enter a valid Ethereum address'; + this.setState({ + errMsg, + }); + this.props.onChange(address, isValidAddress); + }; +} diff --git a/packages/website/ts/pages/instant/configurator.tsx b/packages/website/ts/pages/instant/configurator.tsx index c836739bb..a6c792edf 100644 --- a/packages/website/ts/pages/instant/configurator.tsx +++ b/packages/website/ts/pages/instant/configurator.tsx @@ -1,12 +1,110 @@ +import * as _ from 'lodash'; import * as React from 'react'; import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; +import { ActionLink } from 'ts/pages/instant/action_link'; +import { CodeDemo } from 'ts/pages/instant/code_demo'; +import { ConfigGenerator } from 'ts/pages/instant/config_generator'; import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; + +import { ZeroExInstantBaseConfig } from '../../../../instant/src/types'; export interface ConfiguratorProps { hash: string; } -export const Configurator = (props: ConfiguratorProps) => ( - <Container id={props.hash} height="400px" backgroundColor={colors.instantTertiaryBackground} /> -); +export interface ConfiguratorState { + instantConfig: ZeroExInstantBaseConfig; +} + +export class Configurator extends React.Component<ConfiguratorProps> { + public state: ConfiguratorState = { + instantConfig: { + orderSource: 'https://api.radarrelay.com/0x/v2/', + availableAssetDatas: undefined, + affiliateInfo: { + feeRecipient: '', + feePercentage: 0, + }, + }, + }; + public render(): React.ReactNode { + const { hash } = this.props; + const codeToDisplay = this._generateCodeDemoCode(); + return ( + <Container + className="flex justify-center py4 px3" + id={hash} + backgroundColor={colors.instantTertiaryBackground} + > + <Container className="mx3"> + <Container className="mb3"> + <Text fontSize="20px" lineHeight="28px" fontColor={colors.white} fontWeight={500}> + 0x Instant Configurator + </Text> + </Container> + <ConfigGenerator value={this.state.instantConfig} onConfigChange={this._handleConfigChange} /> + </Container> + <Container className="mx3" height="550px"> + <Container className="mb3 flex justify-between"> + <Text fontSize="20px" lineHeight="28px" fontColor={colors.white} fontWeight={500}> + Code Snippet + </Text> + <ActionLink + displayText="Explore the Docs" + linkSrc={`${WebsitePaths.Wiki}#Get-Started-With-Instant`} + color={colors.grey} + /> + </Container> + <CodeDemo key={codeToDisplay}>{codeToDisplay}</CodeDemo> + </Container> + </Container> + ); + } + private readonly _handleConfigChange = (config: ZeroExInstantBaseConfig) => { + this.setState({ + instantConfig: config, + }); + }; + private readonly _generateCodeDemoCode = (): string => { + const { instantConfig } = this.state; + return `<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <script src="https://instant.0xproject.com/instant.js"></script> + </head> + <body> + <script> + zeroExInstant.render({ + orderSource: '${instantConfig.orderSource}',${ + !_.isUndefined(instantConfig.affiliateInfo) && instantConfig.affiliateInfo.feeRecipient + ? `\n affiliateInfo: { + feeRecipient: '${instantConfig.affiliateInfo.feeRecipient.toLowerCase()}', + feePercentage: ${instantConfig.affiliateInfo.feePercentage} + }` + : '' + }${ + !_.isUndefined(instantConfig.availableAssetDatas) + ? `\n availableAssetDatas: ${this._renderAvailableAssetDatasString( + instantConfig.availableAssetDatas, + )}` + : '' + } + }, 'body'); + </script> + </body> +</html>`; + }; + private readonly _renderAvailableAssetDatasString = (availableAssetDatas: string[]): string => { + const stringAvailableAssetDatas = availableAssetDatas.map(assetData => `'${assetData}'`); + if (availableAssetDatas.length < 2) { + return `[${stringAvailableAssetDatas.join(', ')}]`; + } + return `[\n ${stringAvailableAssetDatas.join( + ', \n ', + )}\n ]`; + }; +} diff --git a/packages/website/ts/pages/instant/features.tsx b/packages/website/ts/pages/instant/features.tsx index 9c1668c1c..ed98ceb53 100644 --- a/packages/website/ts/pages/instant/features.tsx +++ b/packages/website/ts/pages/instant/features.tsx @@ -4,9 +4,9 @@ import * as React from 'react'; import { Container } from 'ts/components/ui/container'; import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; +import { ActionLink, ActionLinkProps } from 'ts/pages/instant/action_link'; import { colors } from 'ts/style/colors'; -import { ScreenWidths } from 'ts/types'; -import { utils } from 'ts/utils/utils'; +import { ScreenWidths, WebsitePaths } from 'ts/types'; export interface FeatureProps { screenWidth: ScreenWidths; @@ -21,7 +21,7 @@ export const Features = (props: FeatureProps) => { }; const exploreTheDocsLinkInfo = { displayText: 'Explore the docs', - linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Get-Started`, + linkSrc: `${WebsitePaths.Wiki}#Get-Started-With-Instant`, }; const tokenLinkInfos = isSmallScreen ? [getStartedLinkInfo] : [getStartedLinkInfo, exploreTheDocsLinkInfo]; return ( @@ -40,7 +40,7 @@ export const Features = (props: FeatureProps) => { linkInfos={[ { displayText: 'Learn about affiliate fees', - linkSrc: `${utils.getCurrentBaseUrl()}/wiki#Learn-About-Affiliate-Fees`, + linkSrc: `${WebsitePaths.Wiki}#Learn-About-Affiliate-Fees`, }, ]} screenWidth={props.screenWidth} @@ -52,7 +52,7 @@ export const Features = (props: FeatureProps) => { linkInfos={[ { displayText: 'Explore AssetBuyer', - linkSrc: `${utils.getCurrentBaseUrl()}/docs/asset-buyer`, + linkSrc: `${WebsitePaths.Docs}/asset-buyer`, }, ]} screenWidth={props.screenWidth} @@ -61,17 +61,11 @@ export const Features = (props: FeatureProps) => { ); }; -interface LinkInfo { - displayText: string; - linkSrc?: string; - onClick?: () => void; -} - interface FeatureItemProps { imgSrc: string; title: string; description: string; - linkInfos: LinkInfo[]; + linkInfos: ActionLinkProps[]; screenWidth: ScreenWidths; } @@ -95,36 +89,11 @@ const FeatureItem = (props: FeatureItemProps) => { </Text> </Container> <Container className="flex" marginTop="28px"> - {_.map(linkInfos, linkInfo => { - const onClick = (event: React.MouseEvent<HTMLElement>) => { - if (!_.isUndefined(linkInfo.onClick)) { - linkInfo.onClick(); - } else if (!_.isUndefined(linkInfo.linkSrc)) { - utils.openUrl(linkInfo.linkSrc); - } - }; - return ( - <Container - key={linkInfo.linkSrc} - className="flex items-center" - marginRight="32px" - onClick={onClick} - cursor="pointer" - > - <Container> - <Text fontSize="16px" fontColor={colors.white}> - {linkInfo.displayText} - </Text> - </Container> - <Container paddingTop="1px" paddingLeft="6px"> - <i - className="zmdi zmdi-chevron-right bold" - style={{ fontSize: 16, color: colors.white }} - /> - </Container> - </Container> - ); - })} + {_.map(linkInfos, linkInfo => ( + <Container key={linkInfo.displayText} marginRight="32px"> + <ActionLink {...linkInfo} /> + </Container> + ))} </Container> </Container> ); diff --git a/packages/website/ts/pages/instant/fee_percentage_slider.tsx b/packages/website/ts/pages/instant/fee_percentage_slider.tsx new file mode 100644 index 000000000..4c92883cb --- /dev/null +++ b/packages/website/ts/pages/instant/fee_percentage_slider.tsx @@ -0,0 +1,72 @@ +import Slider from 'rc-slider'; +import 'rc-slider/assets/index.css'; +import * as React from 'react'; + +import { Text } from 'ts/components/ui/text'; +import { colors } from 'ts/style/colors'; +import { injectGlobal } from 'ts/style/theme'; + +const SliderWithTooltip = (Slider as any).createSliderWithTooltip(Slider); +// tslint:disable-next-line:no-unused-expression +injectGlobal` + .rc-slider-tooltip-inner { + box-shadow: none !important; + background-color: ${colors.white} !important; + border-radius: 4px !important; + padding: 3px 12px !important; + height: auto !important; + position: relative; + top: 7px; + &: after { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-width: 6px; + bottom: 100%; + left: 100%; + border-bottom-color: ${colors.white}; + margin-left: -60%; + } + } +`; + +export interface FeePercentageSliderProps { + value: number; + onChange: (value: number) => void; +} + +export class FeePercentageSlider extends React.Component<FeePercentageSliderProps> { + public render(): React.ReactNode { + return ( + <SliderWithTooltip + min={0} + max={0.05} + step={0.0025} + value={this.props.value} + onChange={this.props.onChange} + tipFormatter={this._feePercentageSliderFormatter} + tipProps={{ placement: 'bottom' }} + trackStyle={{ + backgroundColor: '#b4b4b4', + }} + railStyle={{ + backgroundColor: '#696969', + }} + handleStyle={{ + border: 'none', + boxShadow: 'none', + }} + activeDotStyle={{ + boxShadow: 'none', + border: 'none', + }} + /> + ); + } + private readonly _feePercentageSliderFormatter = (value: number): React.ReactNode => { + return <Text fontColor={colors.black} fontSize="14px" fontWeight={700}>{`${(value * 100).toFixed(2)}%`}</Text>; + }; +} diff --git a/packages/website/ts/pages/instant/instant.tsx b/packages/website/ts/pages/instant/instant.tsx index fa6bd1c33..d72585bfa 100644 --- a/packages/website/ts/pages/instant/instant.tsx +++ b/packages/website/ts/pages/instant/instant.tsx @@ -14,7 +14,7 @@ import { NeedMore } from 'ts/pages/instant/need_more'; import { Screenshots } from 'ts/pages/instant/screenshots'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; -import { ScreenWidths } from 'ts/types'; +import { ScreenWidths, WebsitePaths } from 'ts/types'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; @@ -67,7 +67,7 @@ export class Instant extends React.Component<InstantProps, InstantState> { } private readonly _onGetStartedClick = () => { if (this._isSmallScreen()) { - utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`); + utils.openUrl(`${WebsitePaths.Wiki}#Get-Started-With-Instant`); } else { this._scrollToConfigurator(); } diff --git a/packages/website/ts/pages/instant/need_more.tsx b/packages/website/ts/pages/instant/need_more.tsx index e6d5c3694..70aea7363 100644 --- a/packages/website/ts/pages/instant/need_more.tsx +++ b/packages/website/ts/pages/instant/need_more.tsx @@ -4,7 +4,7 @@ import { Button } from 'ts/components/ui/button'; import { Container } from 'ts/components/ui/container'; import { Text } from 'ts/components/ui/text'; import { colors } from 'ts/style/colors'; -import { ScreenWidths } from 'ts/types'; +import { ScreenWidths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; @@ -58,5 +58,5 @@ const onGetInTouchClick = () => { utils.openUrl(constants.URL_ZEROEX_CHAT); }; const onDocsClick = () => { - utils.openUrl(`${utils.getCurrentBaseUrl()}/wiki#Get-Started`); + utils.openUrl(`${WebsitePaths.Wiki}#Get-Started-With-Instant`); }; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index bb76efe21..d7ed0524c 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -36,8 +36,8 @@ interface Project { } const THROTTLE_TIMEOUT = 100; -const WHATS_NEW_TITLE = 'Introducing the 0x Launch Kit'; -const WHATS_NEW_URL = 'https://blog.0xproject.com/introducing-the-0x-launch-kit-4acdc3453585'; +const WHATS_NEW_TITLE = 'Introducing 0x Instant'; +const WHATS_NEW_URL = WebsitePaths.Instant; const TITLE_STYLE: React.CSSProperties = { fontFamily: 'Roboto Mono', color: colors.grey, @@ -1533,6 +1533,19 @@ version "0.25.38" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.25.38.tgz#6c945fcc91a5fa470cc3c5a4cd4a62d7f8d03576" +"@types/rc-slider@^8.6.0": + version "8.6.0" + resolved "https://registry.npmjs.org/@types/rc-slider/-/rc-slider-8.6.0.tgz#7f061a920b067825c8455cf481c57b0927889c72" + dependencies: + "@types/rc-tooltip" "*" + "@types/react" "*" + +"@types/rc-tooltip@*": + version "3.7.0" + resolved "https://registry.npmjs.org/@types/rc-tooltip/-/rc-tooltip-3.7.0.tgz#6dc9898dc426495baea1b786e5dbde8980bf9737" + dependencies: + "@types/react" "*" + "@types/react-addons-linked-state-mixin@*": version "0.14.19" resolved "https://registry.yarnpkg.com/@types/react-addons-linked-state-mixin/-/react-addons-linked-state-mixin-0.14.19.tgz#7ef00a5618a089da4a99e1f58c9aa4c1781d46d5" @@ -1607,6 +1620,12 @@ dependencies: "@types/react" "*" +"@types/react-syntax-highlighter@^0.0.8": + version "0.0.8" + resolved "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-0.0.8.tgz#ed44e2ead992c513327bcf2ede5eda7daa981421" + dependencies: + "@types/react" "*" + "@types/react-tap-event-plugin@0.0.30": version "0.0.30" resolved "https://registry.yarnpkg.com/@types/react-tap-event-plugin/-/react-tap-event-plugin-0.0.30.tgz#123f35080412f489b6770c5a65c159ff96654cb5" @@ -1975,6 +1994,12 @@ acorn@^6.0.2: version "6.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754" +add-dom-event-listener@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + dependencies: + object-assign "4.x" + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -2937,7 +2962,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@6.x, babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -4183,6 +4208,12 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^1.0.0: + version "1.0.5" + resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db" + dependencies: + trim "0.0.1" + commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -4228,10 +4259,20 @@ compare-versions@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" +component-classes@^1.2.5: + version "1.2.6" + resolved "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691" + dependencies: + component-indexof "0.0.3" + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +component-indexof@0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24" + compressible@~2.0.13: version "2.0.13" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.13.tgz#0d1020ab924b2fdb4d6279875c7d6daba6baa7a9" @@ -4678,6 +4719,13 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" +css-animation@^1.3.2: + version "1.5.0" + resolved "https://registry.npmjs.org/css-animation/-/css-animation-1.5.0.tgz#c96b9097a5ef74a7be8480b45cc44e4ec6ca2bf5" + dependencies: + babel-runtime "6.x" + component-classes "^1.2.5" + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -5320,6 +5368,10 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +dom-align@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/dom-align/-/dom-align-1.8.0.tgz#c0e89b5b674c6e836cd248c52c2992135f093654" + dom-helpers@^3.2.0, dom-helpers@^3.2.1, dom-helpers@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" @@ -6474,6 +6526,12 @@ fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" +fault@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" + dependencies: + format "^0.2.2" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -6776,6 +6834,10 @@ format-util@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95" +format@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -6938,7 +7000,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep: ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-util "^5.2.0" ethereumjs-vm "2.3.5" - ethereumjs-wallet "0.6.0" + ethereumjs-wallet "~0.6.0" fake-merkle-patricia-tree "~1.0.1" heap "~0.2.6" js-scrypt "^0.2.0" @@ -7627,6 +7689,19 @@ hash.js@1.1.3, hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" +hast-util-parse-selector@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz#4ddbae1ae12c124e3eb91b581d2556441766f0ab" + +hastscript@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/hastscript/-/hastscript-5.0.0.tgz#fee10382c1bc4ba3f1be311521d368c047d2c43a" + dependencies: + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.2.0" + property-information "^5.0.1" + space-separated-tokens "^1.0.0" + hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -7667,7 +7742,7 @@ heap@~0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" -highlight.js@^9.0.0, highlight.js@^9.11.0, highlight.js@^9.6.0: +highlight.js@^9.0.0, highlight.js@^9.11.0, highlight.js@^9.6.0, highlight.js@~9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" @@ -9835,7 +9910,7 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" -lodash.keys@^3.0.0: +lodash.keys@^3.0.0, lodash.keys@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" dependencies: @@ -10028,6 +10103,13 @@ lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" +lowlight@~1.9.1: + version "1.9.2" + resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1" + dependencies: + fault "^1.0.2" + highlight.js "~9.12.0" + lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -11152,14 +11234,14 @@ oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" +object-assign@4.x, object-assign@^4, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" -object-assign@^4, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -11608,6 +11690,17 @@ parse-entities@^1.1.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^1.1.2: + version "1.2.0" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -12261,7 +12354,7 @@ pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" -prismjs@^1.15.0: +prismjs@^1.15.0, prismjs@^1.8.4, prismjs@~1.15.0: version "1.15.0" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" optionalDependencies: @@ -12346,7 +12439,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.6.2: +prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: @@ -12361,6 +12454,12 @@ prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, loose-envify "^1.3.1" object-assign "^4.1.1" +property-information@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/property-information/-/property-information-5.0.1.tgz#c3b09f4f5750b1634c0b24205adbf78f18bdf94f" + dependencies: + xtend "^4.0.1" + proto-list@~1.2.1: version "1.2.4" resolved "http://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -12644,6 +12743,66 @@ raw-loader@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" +rc-align@^2.4.0: + version "2.4.3" + resolved "https://registry.npmjs.org/rc-align/-/rc-align-2.4.3.tgz#b9b3c2a6d68adae71a8e1d041cd5e3b2a655f99a" + dependencies: + babel-runtime "^6.26.0" + dom-align "^1.7.0" + prop-types "^15.5.8" + rc-util "^4.0.4" + +rc-animate@2.x: + version "2.6.0" + resolved "https://registry.npmjs.org/rc-animate/-/rc-animate-2.6.0.tgz#ca8440d042781af7a1329d84f97ea94794c5ec15" + dependencies: + babel-runtime "6.x" + classnames "^2.2.6" + css-animation "^1.3.2" + prop-types "15.x" + raf "^3.4.0" + react-lifecycles-compat "^3.0.4" + +rc-slider@^8.6.3: + version "8.6.3" + resolved "https://registry.npmjs.org/rc-slider/-/rc-slider-8.6.3.tgz#1ca0e0bd2863252741de75e7bf8c9f2cfcffccb7" + dependencies: + babel-runtime "6.x" + classnames "^2.2.5" + prop-types "^15.5.4" + rc-tooltip "^3.7.0" + rc-util "^4.0.4" + shallowequal "^1.0.1" + warning "^3.0.0" + +rc-tooltip@^3.7.0: + version "3.7.3" + resolved "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz#280aec6afcaa44e8dff0480fbaff9e87fc00aecc" + dependencies: + babel-runtime "6.x" + prop-types "^15.5.8" + rc-trigger "^2.2.2" + +rc-trigger@^2.2.2: + version "2.6.2" + resolved "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.2.tgz#a9c09ba5fad63af3b2ec46349c7db6cb46657001" + dependencies: + babel-runtime "6.x" + classnames "^2.2.6" + prop-types "15.x" + rc-align "^2.4.0" + rc-animate "2.x" + rc-util "^4.4.0" + +rc-util@^4.0.4, rc-util@^4.4.0: + version "4.6.0" + resolved "https://registry.npmjs.org/rc-util/-/rc-util-4.6.0.tgz#ba33721783192ec4f3afb259e182b04e55deb7f6" + dependencies: + add-dom-event-listener "^1.1.0" + babel-runtime "6.x" + prop-types "^15.5.10" + shallowequal "^0.2.2" + rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: version "1.2.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" @@ -12828,6 +12987,16 @@ react-side-effect@^1.0.2, react-side-effect@^1.1.0: exenv "^1.2.1" shallowequal "^1.0.1" +react-syntax-highlighter@^10.1.1: + version "10.1.1" + resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-10.1.1.tgz#1bf7ad4f2f16d2978b04594407b670671b4d3316" + dependencies: + babel-runtime "^6.18.0" + highlight.js "~9.12.0" + lowlight "~1.9.1" + prismjs "^1.8.4" + refractor "^2.4.1" + react-tabs@^2.0.0: version "2.2.2" resolved "https://registry.npmjs.org/react-tabs/-/react-tabs-2.2.2.tgz#2f2935da379889484751d1df47c1b639e5ee835d" @@ -13157,6 +13326,14 @@ reflect-metadata@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" +refractor@^2.4.1: + version "2.6.2" + resolved "https://registry.npmjs.org/refractor/-/refractor-2.6.2.tgz#8e0877ab8864165275aafeea5d9c8eebe871552f" + dependencies: + hastscript "^5.0.0" + parse-entities "^1.1.2" + prismjs "~1.15.0" + regenerate@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" @@ -14010,6 +14187,12 @@ sha3@^1.1.0: dependencies: nan "2.10.0" +shallowequal@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e" + dependencies: + lodash.keys "^3.1.2" + shallowequal@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f" @@ -14383,6 +14566,12 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" +space-separated-tokens@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412" + dependencies: + trim "0.0.1" + sparkles@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" |