diff options
| -rw-r--r-- | packages/website/ts/components/ui/check_mark.tsx | 31 | ||||
| -rw-r--r-- | packages/website/ts/components/ui/container.tsx | 6 | ||||
| -rw-r--r-- | packages/website/ts/components/ui/multi_select.tsx | 38 | ||||
| -rw-r--r-- | packages/website/ts/components/ui/select.tsx | 2 | ||||
| -rw-r--r-- | packages/website/ts/pages/instant/action_link.tsx | 7 | ||||
| -rw-r--r-- | packages/website/ts/pages/instant/config_generator.tsx | 109 | ||||
| -rw-r--r-- | packages/website/ts/pages/instant/configurator.tsx | 3 | ||||
| -rw-r--r-- | packages/website/ts/pages/instant/features.tsx | 6 | 
8 files changed, 156 insertions, 46 deletions
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 cd0ed9986..4b76ce8be 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -9,6 +9,7 @@ type StringOrNum = string | number;  export type ContainerTag = 'div' | 'span';  export interface ContainerProps { +    margin?: string;      marginTop?: StringOrNum;      marginBottom?: StringOrNum;      marginRight?: StringOrNum; @@ -48,7 +49,9 @@ export interface ContainerProps {      id?: string;      onClick?: (event: React.MouseEvent<HTMLElement>) => void;      overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible'; +    overflowY?: 'scroll' | 'hidden' | 'auto' | 'visible';      shouldDarkenOnHover?: boolean; +    hasBoxShadow?: boolean;      shouldAddBoxShadowOnHover?: boolean;  } @@ -62,6 +65,7 @@ export const PlainContainer: React.StatelessComponent<ContainerProps> = props =>          onClick,          shouldDarkenOnHover,          shouldAddBoxShadowOnHover, +        hasBoxShadow,          // tslint:disable-next-line:trailing-comma          ...style      } = props; @@ -74,6 +78,8 @@ export const PlainContainer: React.StatelessComponent<ContainerProps> = props =>  };  export const Container = styled(PlainContainer)` +    box-sizing: border-box; +    ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};      &:hover {          ${props =>              props.shouldDarkenOnHover diff --git a/packages/website/ts/components/ui/multi_select.tsx b/packages/website/ts/components/ui/multi_select.tsx index bf80443af..2cf44cae1 100644 --- a/packages/website/ts/components/ui/multi_select.tsx +++ b/packages/website/ts/components/ui/multi_select.tsx @@ -2,40 +2,42 @@ 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 MultiSelectItemConfig {      value: string; -    displayText: React.ReactNode; +    renderItemContent: (isSelected: boolean) => React.ReactNode;      onClick?: () => void;  }  export interface MultiSelectProps { -    selectedValues: string[]; +    selectedValues?: string[];      items: MultiSelectItemConfig[];      backgroundColor?: string; -    textColor?: string; +    height?: string;  }  export class MultiSelect extends React.Component<MultiSelectProps> {      public static defaultProps = {          backgroundColor: colors.white, -        textColor: colors.darkGrey,      };      public render(): React.ReactNode { -        const { items, backgroundColor, selectedValues } = this.props; +        const { items, backgroundColor, selectedValues, height } = this.props;          return ( -            <Container backgroundColor={backgroundColor} borderRadius="4px"> +            <Container +                backgroundColor={backgroundColor} +                borderRadius="4px" +                width="100%" +                height={height} +                overflowY="scroll" +            >                  {_.map(items, item => (                      <MultiSelectItem                          key={item.value} -                        displayText={item.displayText} +                        renderItemContent={item.renderItemContent} +                        backgroundColor={backgroundColor}                          onClick={item.onClick} -                        isSelected={_.includes(selectedValues, item.value)} +                        isSelected={_.isUndefined(selectedValues) || _.includes(selectedValues, item.value)}                      />                  ))}              </Container> @@ -44,19 +46,21 @@ export class MultiSelect extends React.Component<MultiSelectProps> {  }  export interface MultiSelectItemProps { -    displayText: React.ReactNode; +    renderItemContent: (isSelected: boolean) => React.ReactNode;      isSelected?: boolean;      onClick?: () => void; +    backgroundColor?: string;  }  export const MultiSelectItem: React.StatelessComponent<MultiSelectItemProps> = ({ -    displayText, +    renderItemContent,      isSelected,      onClick, +    backgroundColor,  }) => ( -    <Container shouldDarkenOnHover={true} onClick={onClick}> -        <Container borderBottom="1px solid black" padding="0px 15px"> -            {displayText} +    <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 index abf202c67..743b082b0 100644 --- a/packages/website/ts/components/ui/select.tsx +++ b/packages/website/ts/components/ui/select.tsx @@ -62,6 +62,7 @@ export class Select extends React.Component<SelectProps, SelectState> {                          cursor={hasItems ? 'pointer' : undefined}                          onClick={this._handleDropdownClick}                          borderRadius={borderRadius} +                        hasBoxShadow={isOpen}                          border={border}                          backgroundColor={backgroundColor}                          padding="0.8em" @@ -94,6 +95,7 @@ export class Select extends React.Component<SelectProps, SelectState> {                              position="absolute"                              onClick={this._closeDropdown}                              zIndex={zIndex.aboveOverlay} +                            hasBoxShadow={true}                          >                              {_.map(items, (item, index) => (                                  <SelectItem diff --git a/packages/website/ts/pages/instant/action_link.tsx b/packages/website/ts/pages/instant/action_link.tsx index 5612dc25b..c196f03ef 100644 --- a/packages/website/ts/pages/instant/action_link.tsx +++ b/packages/website/ts/pages/instant/action_link.tsx @@ -23,12 +23,7 @@ export class ActionLink extends React.Component<ActionLinkProps> {      public render(): React.ReactNode {          const { displayText, fontSize, color, className } = this.props;          return ( -            <Container -                className={`flex items-center ${className}`} -                marginRight="32px" -                onClick={this._handleClick} -                cursor="pointer" -            > +            <Container className={`flex items-center ${className}`} onClick={this._handleClick} cursor="pointer">                  <Container>                      <Text fontSize="16px" fontColor={color}>                          {displayText} diff --git a/packages/website/ts/pages/instant/config_generator.tsx b/packages/website/ts/pages/instant/config_generator.tsx index cd215bc61..d1ea66557 100644 --- a/packages/website/ts/pages/instant/config_generator.tsx +++ b/packages/website/ts/pages/instant/config_generator.tsx @@ -5,6 +5,7 @@ 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'; @@ -54,42 +55,79 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {                  <ConfigGeneratorSection title="Standard Relayer API Endpoint">                      <Select value={value.orderSource} items={this._generateItems()} />                  </ConfigGeneratorSection> -                <ConfigGeneratorSection title="What tokens can users buy?"> +                <ConfigGeneratorSection {...this._getTokenSelectorProps()}>                      {this._renderTokenMultiSelectOrSpinner()}                  </ConfigGeneratorSection>              </Container>          );      } +    private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => { +        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 _getAllKnownAssetDatas = (): string[] => { +        return _.map(this.state.allKnownTokens, token => assetDataUtils.encodeERC20AssetData(token.address)); +    };      private readonly _handleSRASelection = (sraEndpoint: string) => { -        const newConfig = { +        const newConfig: ZeroExInstantBaseConfig = {              ...this.props.value,              orderSource: sraEndpoint,          };          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 = []; -        if (_.includes(value.availableAssetDatas, assetData)) { +        const { allKnownTokens } = this.state; +        let newAvailableAssetDatas: string[] = []; +        const availableAssetDatas = value.availableAssetDatas; +        if (_.isUndefined(availableAssetDatas)) { +            // It being undefined means it's all tokens. +            const allKnownAssetDatas = this._getAllKnownAssetDatas(); +            newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData); +        } else if (!_.includes(availableAssetDatas, assetData)) {              // Add it -            newAvailableAssetDatas = [...value.availableAssetDatas, assetData]; +            newAvailableAssetDatas = [...availableAssetDatas, assetData];          } else {              // Remove it -            newAvailableAssetDatas = _.remove(value.availableAssetDatas, assetData); +            newAvailableAssetDatas = _.pull(availableAssetDatas, assetData);          } -        const newConfig = { +        const newConfig: ZeroExInstantBaseConfig = {              ...this.props.value,              availableAssetDatas: newAvailableAssetDatas,          };          this.props.onConfigChange(newConfig);      }; -    private _setAllKnownTokens = async (callback: () => void): Promise<void> => { +    private readonly _setAllKnownTokens = async (callback: () => void): Promise<void> => {          const tokenInfos = await backendClient.getTokenInfosAsync();          const allKnownTokens = _.reduce(              tokenInfos, @@ -101,7 +139,7 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {          );          this.setState({ allKnownTokens }, callback);      }; -    private _setAvailableAssetsFromOrderProvider = async (): Promise<void> => { +    private readonly _setAvailableAssetsFromOrderProvider = async (): Promise<void> => {          const { value } = this.props;          if (!_.isUndefined(value.orderSource) && _.isString(value.orderSource)) {              this.setState({ isLoadingAvailableTokens: true }); @@ -119,13 +157,23 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {              this.setState({ availableTokens, isLoadingAvailableTokens: false });          }      }; -    private _renderTokenMultiSelectOrSpinner = (): React.ReactNode => { +    private readonly _renderTokenMultiSelectOrSpinner = (): React.ReactNode => {          const { value } = this.props;          const { availableTokens, isLoadingAvailableTokens } = this.state; +        const multiSelectHeight = '200px';          if (isLoadingAvailableTokens) {              return ( -                <Container className="flex items-center"> -                    <Spinner /> +                <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>              );          } @@ -133,15 +181,24 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {              const assetData = assetDataUtils.encodeERC20AssetData(token.address);              return {                  value: assetDataUtils.encodeERC20AssetData(token.address), -                displayText: ( -                    <Text> -                        <b>{token.symbol}</b> - {token.name} -                    </Text> +                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>{token.symbol}</b> — {token.name} +                        </Text> +                    </Container>                  ),                  onClick: this._handleTokenClick.bind(this, assetData),              };          }); -        return <MultiSelect items={items} selectedValues={value.availableAssetDatas || []} />; +        return <MultiSelect items={items} selectedValues={value.availableAssetDatas} height={multiSelectHeight} />;      };  } @@ -151,13 +208,23 @@ export interface ConfigGeneratorSectionProps {      onActionTextClick?: () => void;  } -export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = props => ( +export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = ({ +    title, +    actionText, +    onActionTextClick, +    children, +}) => (      <Container marginBottom="30px"> -        <Container marginBottom="10px"> +        <Container marginBottom="10px" className="flex justify-between items-center">              <Text fontColor={colors.white} fontSize="16px" lineHeight="18px"> -                {props.title} +                {title}              </Text> +            {actionText && ( +                <Text fontSize="12px" fontColor={colors.grey} onClick={onActionTextClick}> +                    {actionText} +                </Text> +            )}          </Container> -        {props.children} +        {children}      </Container>  ); diff --git a/packages/website/ts/pages/instant/configurator.tsx b/packages/website/ts/pages/instant/configurator.tsx index cf9985675..f4987c0de 100644 --- a/packages/website/ts/pages/instant/configurator.tsx +++ b/packages/website/ts/pages/instant/configurator.tsx @@ -18,9 +18,10 @@ export interface ConfiguratorState {  }  export class Configurator extends React.Component<ConfiguratorProps> { -    public state = { +    public state: ConfiguratorState = {          instantConfig: {              orderSource: 'https://api.radarrelay.com/0x/v2/', +            availableAssetDatas: [],          },      };      public render(): React.ReactNode { diff --git a/packages/website/ts/pages/instant/features.tsx b/packages/website/ts/pages/instant/features.tsx index 1e187c9da..c88958bbf 100644 --- a/packages/website/ts/pages/instant/features.tsx +++ b/packages/website/ts/pages/instant/features.tsx @@ -90,7 +90,11 @@ const FeatureItem = (props: FeatureItemProps) => {                  </Text>              </Container>              <Container className="flex" marginTop="28px"> -                {_.map(linkInfos, linkInfo => <ActionLink key={linkInfo.displayText} {...linkInfo} />)} +                {_.map(linkInfos, linkInfo => ( +                    <Container marginRight="32px"> +                        <ActionLink key={linkInfo.displayText} {...linkInfo} /> +                    </Container> +                ))}              </Container>          </Container>      );  | 
