Styled configurator

This commit is contained in:
Fred Carlsen
2018-12-13 17:38:18 +01:00
parent 803086da57
commit 1c413e632b
6 changed files with 398 additions and 31 deletions

View File

@@ -10,7 +10,9 @@ interface BaseTextInterface extends PaddingInterface {
interface HeadingProps extends BaseTextInterface {
asElement?: 'h1'| 'h2'| 'h3'| 'h4';
maxWidth?: string;
fontWeight?: string;
isCentered?: boolean;
isFlex?: boolean;
isNoMargin?: boolean;
isMuted?: boolean | number;
marginBottom?: string;
@@ -26,6 +28,9 @@ interface ParagraphProps extends BaseTextInterface {
const StyledHeading = styled.h1<HeadingProps>`
max-width: ${props => props.maxWidth};
color: ${props => props.color || props.theme.textColor};
display: ${props => props.isFlex && `inline-flex`};
align-items: center;
justify-content: ${props => props.isFlex && `space-between`};
font-size: ${props => isNaN(props.size) ? `var(--${props.size || 'default'}Heading)` : `${props.size}px`};
line-height: ${props => `var(--${props.size || 'default'}HeadingHeight)`};
text-align: ${props => props.isCentered && 'center'};
@@ -34,7 +39,8 @@ const StyledHeading = styled.h1<HeadingProps>`
margin-right: ${props => props.isCentered && 'auto'};
margin-bottom: ${props => !props.isNoMargin && (props.marginBottom || '30px')};
opacity: ${props => typeof props.isMuted === 'boolean' ? 0.75 : props.isMuted};
font-weight: ${props => ['h4'].includes(props.asElement) ? 400 : 300};
font-weight: ${props => props.fontWeight ? props.fontWeight : (['h4'].includes(props.asElement) ? 400 : 300)};
width: ${props => props.isFlex && `100%`};
`;
export const Heading: React.StatelessComponent<HeadingProps> = props => {

View File

@@ -0,0 +1,179 @@
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: 100%;
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: #060D0D !important;
color: #999;
min-height: 100%;
text-align: center;
margin-right: 15px;
line-height: 25px;
padding: 10px 7px !important;
}
code:last-of-type {
position: relative;
top: 10px;
top: 0;
padding-top: 11px;
display: inline-block;
line-height: 25px;
}
`;
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: '#61f5ff',
},
'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: '#1B2625',
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 });
};
}

View File

@@ -4,14 +4,14 @@ import { assetDataUtils } from '@0x/order-utils';
import { ObjectMap } from '@0x/types';
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
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 { ConfigGeneratorAddressInput } from 'ts/@next/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';
@@ -19,6 +19,7 @@ import { constants } from 'ts/utils/constants';
// New components
import { Heading, Paragraph } from 'ts/@next/components/text';
import { Select, SelectItemConfig } from 'ts/@next/pages/instant/select';
import { assetMetaDataMap } from '../../../../../instant/src/data/asset_meta_data_map';
import { ERC20AssetMetaData, ZeroExInstantBaseConfig } from '../../../../../instant/src/types';
@@ -62,8 +63,8 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps, Confi
}
return (
<Container minWidth="350px">
<ConfigGeneratorSection title="Standard relayer API endpoint">
<Select value={value.orderSource} items={this._generateItems()} />
<ConfigGeneratorSection title="Liquidity Source">
<Select id="" value={value.orderSource} items={this._generateItems()} />
</ConfigGeneratorSection>
<ConfigGeneratorSection {...this._getTokenSelectorProps()}>
{this._renderTokenMultiSelectOrSpinner()}
@@ -113,7 +114,8 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps, Confi
};
private readonly _generateItems = (): SelectItemConfig[] => {
return _.map(SRA_ENDPOINTS, endpoint => ({
text: endpoint,
label: endpoint,
value: endpoint,
onClick: this._handleSRASelection.bind(this, endpoint),
}));
};
@@ -252,15 +254,9 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps, Confi
renderItemContent: (isSelected: boolean) => (
<Container className="flex items-center">
<Container marginRight="10px">
<CheckMark isChecked={isSelected} />
<CheckMark isChecked={isSelected} color={colors.brandLight} />
</Container>
<Text
fontSize="16px"
fontColor={isSelected ? colors.mediumBlue : colors.darkerGrey}
fontWeight={300}
>
<b>{metaData.symbol.toUpperCase()}</b> {metaData.name}
</Text>
<CheckboxText isSelected={isSelected}>{metaData.symbol.toUpperCase()} {metaData.name}</CheckboxText>
</Container>
),
onClick: this._handleTokenClick.bind(this, assetData),
@@ -288,27 +284,57 @@ export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSec
}) => (
<Container marginBottom={marginBottom}>
<Container marginBottom="10px" className="flex justify-between items-center">
<Container>
<Heading size="small" isNoMargin={true}>
{title}
</Heading>
<Heading size="small" marginBottom="0" isFlex={true}>
<span>{title}</span>
{isOptional && (
<Text fontColor={colors.grey} fontSize="16px" lineHeight="18px" display="inline">
{' '}
(optional)
</Text>
<OptionalText>
{' '}
Optional
</OptionalText>
)}
</Container>
</Heading>
{actionText && (
<Text fontSize="12px" fontColor={colors.grey} onClick={onActionTextClick}>
<OptionalAction onClick={onActionTextClick}>
{actionText}
</Text>
</OptionalAction>
)}
</Container>
{children}
</Container>
);
const Mark = ({ checked }) => (
<StyledMark checked={checked}>
{checked && ''}
</StyledMark>
);
const StyledMark = styled.div`
border: 1px solid #ccc;
border-radius: 50%;
width: 16px;
height: 16px;
border-color: ${props => props.checked && colors.brandLight};
background-color: ${props => props.checked && colors.brandLight};
`;
ConfigGeneratorSection.defaultProps = {
marginBottom: '30px',
};
const OptionalText = styled.span`
display: inline;
font-size: 14px;
color: #999999;
flex-shrink: 0;
`;
const CheckboxText = styled.span`
font-size: 14px;
line-height: 18px;
color: ${props => props.isSelected ? colors.brandDark : '#666666'}
`;
const OptionalAction = styled(OptionalText)`
cursor: pointer;
`;

View File

@@ -0,0 +1,90 @@
import { addressUtils } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import { colors } from 'ts/style/colors';
import { Container } from 'ts/components/ui/container';
import { Text } from 'ts/components/ui/text';
import { Paragraph } from 'ts/@next/components/text';
export interface ConfigGeneratorAddressInputProps {
value?: string;
onChange?: (address: string, isValid: boolean) => void;
}
export interface ConfigGeneratorAddressInputState {
errMsg: string;
}
export interface InputProps {
className?: string;
value?: string;
width?: string;
fontSize?: string;
fontColor?: string;
border?: string;
padding?: string;
placeholderColor?: string;
placeholder?: string;
backgroundColor?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
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
value={this.props.value}
onChange={this._handleChange}
placeholder="0xe99...aa8da4"
/>
<Container marginTop="5px" isHidden={!hasError} height="25px">
<Paragraph size="small" isNoMargin={true}>
{errMsg}
</Paragraph>
</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);
};
}
const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => (
<input className={className} value={value} onChange={onChange} placeholder={placeholder} />
);
export const Input = styled(PlainInput)`
background-color: ${colors.white};
color: ${colors.textDarkSecondary};
font-size: 1rem;
width: 100%;
padding: 16px 20px 18px;
border-radius: 4px;
border: 1px solid transparent;
outline: none;
&::placeholder {
color: #333333;
opacity: 0.5;
}
`;

View File

@@ -4,8 +4,8 @@ import styled from 'styled-components';
import { colors } from 'ts/style/colors';
import { CodeDemo } from 'ts/@next/pages/instant/code_demo';
import { ConfigGenerator } from 'ts/@next/pages/instant/config_generator';
import { CodeDemo } from 'ts/pages/instant/code_demo';
import { Column, FlexWrap, Section } from 'ts/@next/components/newLayout';
import { Heading, Paragraph } from 'ts/@next/components/text';
import { WebsitePaths } from 'ts/types';
@@ -44,7 +44,7 @@ export class Configurator extends React.Component<ConfiguratorProps> {
</Column>
<Column width="560px">
<HeadingWrapper>
<Heading size="small">Code Snippet</Heading>
<Heading size="small" marginBottom="15px">Code Snippet</Heading>
<Link
href={`${WebsitePaths.Wiki}#Get-Started-With-Instant`}
isBlock={true}
@@ -87,10 +87,10 @@ export class Configurator extends React.Component<ConfiguratorProps> {
)}`
: ''
}
}, 'body');
</script>
</body>
</html>`;
}, 'body');
</script>
</body>
</html>`;
};
private readonly _renderAvailableAssetDatasString = (availableAssetDatas: string[]): string => {
const stringAvailableAssetDatas = availableAssetDatas.map(assetData => `'${assetData}'`);

View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import styled from 'styled-components';
import { colors } from 'ts/style/colors';
import {Column, Section, Wrap, WrapCentered} from 'ts/@next/components/layout';
import {Heading, Paragraph} from 'ts/@next/components/text';
export interface SelectItemConfig {
label: string;
value?: string;
onClick?: () => void;
}
interface SelectProps {
value?: string;
id: string;
items: SelectItemConfig[];
emptyText?: string;
}
export const Select: React.FunctionComponent<SelectProps> = ({ value, id, items, emptyText }) => {
return (
<Container>
<StyledSelect id={id}>
<option value="">{emptyText}</option>
{items.map((item, index) => <option key={`${id}-item-${index}`} value={item.value} selected={item.value === value} onClick={item.onClick}>{item.label}</option>)}
</StyledSelect>
<Caret width="12" height="7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11 1L6 6 1 1" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></Caret>
</Container>
);
};
Select.defaultProps = {
emptyText: 'Select...',
};
const Container = styled.div`
background-color: #fff;
border-radius: 4px;
display: flex;
width: 100%;
position: relative;
`;
const StyledSelect = styled.select`
appearance: none;
border: 0;
font-size: 1rem;
width: 100%;
padding: 20px 20px 20px 20px;
`;
const SelectAllButton = styled.button`
appearance: none;
border: 0;
font-size: 0.777777778rem;
display: block;
opacity: 0.75;
`;
const Caret = styled.svg`
position: absolute;
right: 20px;
top: calc(50% - 4px);
`;