aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfragosti <francesco.agosti93@gmail.com>2018-12-01 02:25:36 +0800
committerFred Carlsen <fred@sjelfull.no>2018-12-06 18:56:11 +0800
commita849af8a48ac9346ff0da92b4c643cd66dfd855b (patch)
tree02f092902ee793d828fdae219d8ac76f9002f1fe
parentcc235aac3866c44a7b27bb0343390224a02b8c55 (diff)
downloaddexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar.gz
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar.bz2
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar.lz
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar.xz
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.tar.zst
dexon-sol-tools-a849af8a48ac9346ff0da92b4c643cd66dfd855b.zip
feat: implement multi token select component
-rw-r--r--packages/website/ts/components/ui/check_mark.tsx31
-rw-r--r--packages/website/ts/components/ui/container.tsx6
-rw-r--r--packages/website/ts/components/ui/multi_select.tsx38
-rw-r--r--packages/website/ts/components/ui/select.tsx2
-rw-r--r--packages/website/ts/pages/instant/action_link.tsx7
-rw-r--r--packages/website/ts/pages/instant/config_generator.tsx109
-rw-r--r--packages/website/ts/pages/instant/configurator.tsx3
-rw-r--r--packages/website/ts/pages/instant/features.tsx6
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>
);