diff --git a/packages/instant/package.json b/packages/instant/package.json index 0f6f125fa6..32fbe08a44 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -54,6 +54,7 @@ "@0x/typescript-typings": "^3.0.3", "@0x/utils": "^2.0.3", "@0x/web3-wrapper": "^3.1.0", + "copy-to-clipboard": "^3.0.8", "ethereum-types": "^1.1.1", "lodash": "^4.17.10", "polished": "^2.2.0", diff --git a/packages/instant/src/components/animations/position_animation.tsx b/packages/instant/src/components/animations/position_animation.tsx index 576d29c07d..8b3b294b7c 100644 --- a/packages/instant/src/components/animations/position_animation.tsx +++ b/packages/instant/src/components/animations/position_animation.tsx @@ -77,6 +77,7 @@ const generatePositionAnimationCss = (positionSettings: PositionAnimationSetting export interface PositionAnimationProps { positionSettings: OptionallyScreenSpecific; zIndex?: OptionallyScreenSpecific; + height?: string; } const defaultAnimation = (positionSettings: OptionallyScreenSpecific) => { @@ -104,5 +105,6 @@ export const PositionAnimation = ${props => animationForSize(props.positionSettings, 'sm', media.small)} ${props => animationForSize(props.positionSettings, 'md', media.medium)} ${props => animationForSize(props.positionSettings, 'lg', media.large)} + ${props => (props.height ? `height: ${props.height};` : '')} } `; diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx index 122229dee8..9adb1c6743 100644 --- a/packages/instant/src/components/animations/slide_animation.tsx +++ b/packages/instant/src/components/animations/slide_animation.tsx @@ -10,6 +10,7 @@ export interface SlideAnimationProps { slideInSettings: OptionallyScreenSpecific; slideOutSettings: OptionallyScreenSpecific; zIndex?: OptionallyScreenSpecific; + height?: string; } export const SlideAnimation: React.StatelessComponent = props => { @@ -18,7 +19,7 @@ export const SlideAnimation: React.StatelessComponent = pro } const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings; return ( - + {props.children} ); diff --git a/packages/instant/src/components/css_reset.tsx b/packages/instant/src/components/css_reset.tsx index a1dd2e05c0..0bef853899 100644 --- a/packages/instant/src/components/css_reset.tsx +++ b/packages/instant/src/components/css_reset.tsx @@ -27,7 +27,6 @@ export const CSSReset = createGlobalStyle` text-align: left; text-decoration: none; vertical-align: baseline; - z-index: 1; } } `; diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index f21c21b87e..520ac33d5e 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -113,7 +113,7 @@ export class ERC20AssetAmountInput extends React.Component - + ); }; diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index 76d5c66ffa..3503ff31af 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -28,14 +28,14 @@ export class ERC20TokenSelector extends React.Component public render(): React.ReactNode { const { tokens, onTokenSelect } = this.props; return ( - + - + {_.map(tokens, token => { if (!this._isTokenQueryMatch(token)) { return null; @@ -85,7 +85,7 @@ class TokenSelectorRow extends React.Component { - + {displaySymbol} diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx new file mode 100644 index 0000000000..8c0b47d726 --- /dev/null +++ b/packages/instant/src/components/payment_method.tsx @@ -0,0 +1,45 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { Network } from '../types'; + +import { PaymentMethodDropdown } from './payment_method_dropdown'; +import { Circle } from './ui/circle'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface PaymentMethodProps {} + +export const PaymentMethod: React.StatelessComponent = () => ( + + + + + Payment Method + + + + + + MetaMask + + + + + + + +); diff --git a/packages/instant/src/components/payment_method_dropdown.tsx b/packages/instant/src/components/payment_method_dropdown.tsx new file mode 100644 index 0000000000..bdce2a49d2 --- /dev/null +++ b/packages/instant/src/components/payment_method_dropdown.tsx @@ -0,0 +1,44 @@ +import { BigNumber } from '@0x/utils'; +import copy from 'copy-to-clipboard'; +import * as React from 'react'; + +import { Network } from '../types'; +import { etherscanUtil } from '../util/etherscan'; +import { format } from '../util/format'; + +import { Dropdown, DropdownItemConfig } from './ui/dropdown'; + +export interface PaymentMethodDropdownProps { + accountAddress: string; + accountEthBalanceInWei?: BigNumber; + network: Network; +} + +export class PaymentMethodDropdown extends React.Component { + public render(): React.ReactNode { + const { accountAddress, accountEthBalanceInWei } = this.props; + const value = format.ethAddress(accountAddress); + const label = format.ethBaseAmount(accountEthBalanceInWei, 4, '') as string; + return ; + } + private readonly _getDropdownItemConfigs = (): DropdownItemConfig[] => { + const viewOnEtherscan = { + text: 'View on Etherscan', + onClick: this._handleEtherscanClick, + }; + const copyAddressToClipboard = { + text: 'Copy address to clipboard', + onClick: this._handleCopyToClipboardClick, + }; + return [viewOnEtherscan, copyAddressToClipboard]; + }; + private readonly _handleEtherscanClick = (): void => { + const { accountAddress, network } = this.props; + const etherscanUrl = etherscanUtil.getEtherScanEthAddressIfExists(accountAddress, network); + window.open(etherscanUrl, '_blank'); + }; + private readonly _handleCopyToClipboardClick = (): void => { + const { accountAddress } = this.props; + copy(accountAddress); + }; +} diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx index 462199d787..a8d4e391c3 100644 --- a/packages/instant/src/components/sliding_error.tsx +++ b/packages/instant/src/components/sliding_error.tsx @@ -86,7 +86,7 @@ export const SlidingError: React.StatelessComponent = props = diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx index a5d15c401c..9d16f95606 100644 --- a/packages/instant/src/components/sliding_panel.tsx +++ b/packages/instant/src/components/sliding_panel.tsx @@ -30,7 +30,9 @@ export const Panel: React.StatelessComponent = ({ title, children, o - {children} + + {children} + ); @@ -67,6 +69,7 @@ export const SlidingPanel: React.StatelessComponent = props = slideInSettings={slideUpSettings} slideOutSettings={slideDownSettings} animationState={animationState} + height="100%" > diff --git a/packages/instant/src/components/ui/circle.tsx b/packages/instant/src/components/ui/circle.tsx index 26764ec71e..4f9f56f125 100644 --- a/packages/instant/src/components/ui/circle.tsx +++ b/packages/instant/src/components/ui/circle.tsx @@ -1,24 +1,27 @@ -import { styled } from '../../style/theme'; +import { ColorOption, styled, Theme, withTheme } from '../../style/theme'; export interface CircleProps { diameter: number; - fillColor?: string; + rawColor?: string; + color?: ColorOption; + theme: Theme; } -export const Circle = +export const Circle = withTheme( styled.div < - CircleProps > - ` + CircleProps > + ` && { width: ${props => props.diameter}px; height: ${props => props.diameter}px; - background-color: ${props => props.fillColor}; + background-color: ${props => (props.rawColor ? props.rawColor : props.theme[props.color || ColorOption.white])}; border-radius: 50%; } -`; +`, +); Circle.displayName = 'Circle'; Circle.defaultProps = { - fillColor: 'white', + color: ColorOption.white, }; diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index c42082ed5f..8aa5db9e52 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -34,6 +34,7 @@ export interface ContainerProps { cursor?: string; overflow?: string; darkenOnHover?: boolean; + boxShadowOnHover?: boolean; flexGrow?: string | number; } @@ -42,7 +43,6 @@ export const Container = ContainerProps > ` && { - all: initial; box-sizing: border-box; ${props => cssRuleIfExists(props, 'flex-grow')} ${props => cssRuleIfExists(props, 'position')} @@ -79,6 +79,7 @@ export const Container = props.backgroundColor ? darken(0.05, props.theme[props.backgroundColor]) : 'none' }` : ''}; + ${props => (props.boxShadowOnHover ? 'box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)' : '')}; } } `; diff --git a/packages/instant/src/components/ui/dropdown.tsx b/packages/instant/src/components/ui/dropdown.tsx new file mode 100644 index 0000000000..3a23f456dd --- /dev/null +++ b/packages/instant/src/components/ui/dropdown.tsx @@ -0,0 +1,134 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption, completelyTransparent } from '../../style/theme'; +import { zIndex } from '../../style/z_index'; + +import { Container } from './container'; +import { Flex } from './flex'; +import { Icon } from './icon'; +import { Overlay } from './overlay'; +import { Text } from './text'; + +export interface DropdownItemConfig { + text: string; + onClick?: () => void; +} + +export interface DropdownProps { + value: string; + label?: string; + items: DropdownItemConfig[]; +} + +export interface DropdownState { + isOpen: boolean; +} + +export class Dropdown extends React.Component { + public static defaultProps = { + items: [], + }; + public state: DropdownState = { + isOpen: false, + }; + public render(): React.ReactNode { + const { value, label, items } = this.props; + const { isOpen } = this.state; + const hasItems = !_.isEmpty(items); + const borderRadius = isOpen ? '4px 4px 0px 0px' : '4px'; + return ( + + {isOpen && ( + + )} + + + + + {value} + + + {label && ( + + {label} + + )} + {hasItems && ( + + + + )} + + + + {isOpen && ( + + {_.map(items, (item, index) => ( + + ))} + + )} + + + ); + } + private readonly _handleDropdownClick = (): void => { + if (_.isEmpty(this.props.items)) { + return; + } + this.setState({ + isOpen: !this.state.isOpen, + }); + }; + private readonly _closeDropdown = (): void => { + this.setState({ + isOpen: false, + }); + }; +} + +export interface DropdownItemProps extends DropdownItemConfig { + text: string; + onClick?: () => void; + isLast: boolean; +} + +export const DropdownItem: React.StatelessComponent = ({ text, onClick, isLast }) => ( + + + {text} + + +); diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx index 5b00138b85..274c46b9ee 100644 --- a/packages/instant/src/components/ui/flex.tsx +++ b/packages/instant/src/components/ui/flex.tsx @@ -19,7 +19,6 @@ export const Flex = FlexProps > ` && { - all: initial; display: ${props => (props.inline ? 'inline-flex' : 'flex')}; flex-direction: ${props => props.direction}; flex-wrap: ${props => props.flexWrap}; diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx index 2679dad1ac..a88fa87ddb 100644 --- a/packages/instant/src/components/ui/icon.tsx +++ b/packages/instant/src/components/ui/icon.tsx @@ -9,7 +9,6 @@ interface IconInfo { path: string; fillRule?: svgRule; clipRule?: svgRule; - stroke?: string; strokeOpacity?: number; strokeWidth?: number; strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit'; @@ -47,7 +46,6 @@ const ICONS: IconInfoMapping = { chevron: { viewBox: '0 0 12 7', path: 'M11 1L6 6L1 1', - stroke: 'white', strokeOpacity: 0.5, strokeWidth: 1.5, strokeLinecap: 'round', @@ -67,6 +65,7 @@ export interface IconProps { width: number; height?: number; color?: ColorOption; + stroke?: ColorOption; icon: keyof IconInfoMapping; onClick?: (event: React.MouseEvent) => void; padding?: string; @@ -75,6 +74,7 @@ export interface IconProps { const PlainIcon: React.StatelessComponent = props => { const iconInfo = ICONS[props.icon]; const colorValue = _.isUndefined(props.color) ? undefined : props.theme[props.color]; + const strokeValue = _.isUndefined(props.stroke) ? undefined : props.theme[props.stroke]; return (
= props => { fill={colorValue} fillRule={iconInfo.fillRule || 'nonzero'} clipRule={iconInfo.clipRule || 'nonzero'} - stroke={iconInfo.stroke} + stroke={strokeValue} strokeOpacity={iconInfo.strokeOpacity} strokeWidth={iconInfo.strokeWidth} strokeLinecap={iconInfo.strokeLinecap} @@ -102,7 +102,8 @@ const PlainIcon: React.StatelessComponent = props => { export const Icon = withTheme(styled(PlainIcon)` && { - cursor: ${props => (!_.isUndefined(props.onClick) ? 'pointer' : 'default')}; + display: inline-block; + ${props => (!_.isUndefined(props.onClick) ? 'cursor: pointer' : '')}; transition: opacity 0.5s ease; padding: ${props => props.padding}; opacity: ${props => (!_.isUndefined(props.onClick) ? 0.7 : 1)}; diff --git a/packages/instant/src/components/ui/overlay.tsx b/packages/instant/src/components/ui/overlay.tsx index 8c9572615a..c5f55f9c03 100644 --- a/packages/instant/src/components/ui/overlay.tsx +++ b/packages/instant/src/components/ui/overlay.tsx @@ -1,29 +1,17 @@ import * as _ from 'lodash'; -import * as React from 'react'; -import { ColorOption, overlayBlack, styled } from '../../style/theme'; - -import { Container } from './container'; -import { Flex } from './flex'; -import { Icon } from './icon'; +import { overlayBlack, styled } from '../../style/theme'; +import { zIndex } from '../../style/z_index'; export interface OverlayProps { - className?: string; - onClose?: () => void; zIndex?: number; + backgroundColor?: string; } -const PlainOverlay: React.StatelessComponent = ({ children, className, onClose }) => ( - - - - - - {children} - - -); -export const Overlay = styled(PlainOverlay)` +export const Overlay = + styled.div < + OverlayProps > + ` && { position: fixed; top: 0; @@ -31,12 +19,13 @@ export const Overlay = styled(PlainOverlay)` bottom: 0; left: 0; z-index: ${props => props.zIndex} - background-color: ${overlayBlack}; + background-color: ${props => props.backgroundColor}; } `; Overlay.defaultProps = { - zIndex: 100, + zIndex: zIndex.overlayDefault, + backgroundColor: overlayBlack, }; Overlay.displayName = 'Overlay'; diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index c6a76ff18a..4fe429d25a 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -28,7 +28,6 @@ export const Text = TextProps > ` && { - all: initial; font-family: 'Inter UI', sans-serif; font-style: ${props => props.fontStyle}; font-weight: ${props => props.fontWeight}; diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index d2216b54f8..5748e064ed 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -3,11 +3,9 @@ import * as React from 'react'; import { AvailableERC20TokenSelector } from '../containers/available_erc20_token_selector'; import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details'; import { LatestError } from '../containers/latest_error'; +import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress'; import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons'; import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading'; - -import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress'; - import { ColorOption } from '../style/theme'; import { zIndex } from '../style/z_index'; diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx index 3461600e18..10438ab7a5 100644 --- a/packages/instant/src/components/zero_ex_instant_overlay.tsx +++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx @@ -1,5 +1,10 @@ import * as React from 'react'; +import { ColorOption } from '../style/theme'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Icon } from './ui/icon'; import { Overlay } from './ui/overlay'; import { ZeroExInstantContainer } from './zero_ex_instant_container'; import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider'; @@ -13,8 +18,22 @@ export const ZeroExInstantOverlay: React.StatelessComponent - - + + + + + + + + + ); diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts index 8dada2d28c..2653c38f7e 100644 --- a/packages/instant/src/style/theme.ts +++ b/packages/instant/src/style/theme.ts @@ -15,6 +15,8 @@ export enum ColorOption { white = 'white', lightOrange = 'lightOrange', darkOrange = 'darkOrange', + green = 'green', + red = 'red', } export const theme: Theme = { @@ -28,9 +30,12 @@ export const theme: Theme = { white: 'white', lightOrange: '#F9F2ED', darkOrange: '#F2994C', + green: '#3CB34F', + red: '#D00000', }; export const transparentWhite = 'rgba(255,255,255,0.3)'; export const overlayBlack = 'rgba(0, 0, 0, 0.6)'; +export const completelyTransparent = 'rga(0, 0, 0, 0)'; export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider }; diff --git a/packages/instant/src/style/z_index.ts b/packages/instant/src/style/z_index.ts index 03623f044d..bd034182ec 100644 --- a/packages/instant/src/style/z_index.ts +++ b/packages/instant/src/style/z_index.ts @@ -1,6 +1,8 @@ export const zIndex = { - errorPopBehind: 1, - mainContainer: 2, - panel: 3, - errorPopUp: 4, + errorPopBehind: 10, + mainContainer: 20, + dropdownItems: 30, + panel: 40, + errorPopup: 50, + overlayDefault: 100, }; diff --git a/packages/instant/src/util/etherscan.ts b/packages/instant/src/util/etherscan.ts index cfc2578a3f..4d62c4d9f0 100644 --- a/packages/instant/src/util/etherscan.ts +++ b/packages/instant/src/util/etherscan.ts @@ -21,4 +21,11 @@ export const etherscanUtil = { } return `https://${prefix}etherscan.io/tx/${txHash}`; }, + getEtherScanEthAddressIfExists: (ethAddress: string, networkId: number) => { + const prefix = etherscanPrefix(networkId); + if (_.isUndefined(prefix)) { + return; + } + return `https://${prefix}etherscan.io/address/${ethAddress}`; + }, }; diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index 4a48dec9df..44661d6974 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -50,4 +50,7 @@ export const format = { } return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`; }, + ethAddress: (address: string): string => { + return `0x${address.slice(2, 7)}…${address.slice(-5)}`; + }, }; diff --git a/yarn.lock b/yarn.lock index e550dce25b..824c2eea0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4395,6 +4395,12 @@ copy-to-clipboard@^3: dependencies: toggle-selection "^1.0.3" +copy-to-clipboard@^3.0.8: + version "3.0.8" + resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" + dependencies: + toggle-selection "^1.0.3" + copyfiles@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.0.0.tgz#bbd78bb78e8fd6db5c67adf54249317b24560f2a"